From 53224a5a64e4a31454e496b18f84aa96b2b5c714 Mon Sep 17 00:00:00 2001 From: Andreas Geissler Date: Thu, 5 Jun 2025 13:40:43 +0200 Subject: [PATCH] [DOC] Add ArgoCD deployment example and guide - add argo application resources for infrastructure and onap charts - add documentation for ArgoCD deployment - add release notes for Paris release - remove obsolete configration in policy-opa-pdp - fix some linter warnings and disable checkbashisms check for local variables Issue-ID: OOM-3346 Change-Id: I053c11309f2ef7f9bdabd00386897d9bf83d1d56 Signed-off-by: Andreas Geissler --- .ci/check-bashisms.sh | 2 +- argo/argocd/app-argocd.yaml | 34 + argo/argocd/argo-project.yaml | 35 + argo/argocd/argo-secret.yaml | 39 + argo/argocd/argocd.yaml | 47 + argo/argocd/kustomization.yaml | 24 + argo/argocd/values/argocd.yaml | 38 + argo/infra/app-infra.yaml | 34 + argo/infra/cert-manager.yaml | 49 + argo/infra/chartmuseum.yaml | 47 + argo/infra/compile-onap.yaml | 66 + argo/infra/compile-onap/helm/Chart.yaml | 22 + .../helm/templates/onap-helm-render-job.yaml | 59 + argo/infra/compile-onap/helm/values.yaml | 23 + argo/infra/django-defectdojo.yaml | 51 + argo/infra/gateway-api.yaml | 42 + ...ateway.networking.k8s.io_backendlbpolicies.yaml | 497 ++ ...teway.networking.k8s.io_backendtlspolicies.yaml | 625 ++ .../gateway.networking.k8s.io_gatewayclasses.yaml | 516 ++ .../gateway.networking.k8s.io_gateways.yaml | 2496 ++++++++ .../gateway.networking.k8s.io_grpcroutes.yaml | 2234 +++++++ .../gateway.networking.k8s.io_httproutes.yaml | 6158 ++++++++++++++++++++ .../gateway.networking.k8s.io_referencegrants.yaml | 189 + .../gateway.networking.k8s.io_tcproutes.yaml | 741 +++ .../gateway.networking.k8s.io_tlsroutes.yaml | 804 +++ .../gateway.networking.k8s.io_udproutes.yaml | 741 +++ argo/infra/gateway-api/crd/kustomization.yaml | 29 + .../gateway.networking.k8s.io_gatewayclasses.yaml | 478 ++ .../gateway.networking.k8s.io_gateways.yaml | 2188 +++++++ .../gateway.networking.k8s.io_grpcroutes.yaml | 2009 +++++++ .../gateway.networking.k8s.io_httproutes.yaml | 5528 ++++++++++++++++++ .../gateway.networking.k8s.io_referencegrants.yaml | 189 + argo/infra/gateway-api/kustomization.yaml | 23 + argo/infra/infra-ingress.yaml | 60 + argo/infra/ingress-routes/helm/Chart.yaml | 22 + .../ingress-routes/helm/templates/gateway.yaml | 164 + .../helm/templates/ingress-argocd.yaml | 57 + .../helm/templates/ingress-defectdojo.yaml | 58 + .../helm/templates/ingress-grafana.yaml | 54 + .../helm/templates/ingress-jaeger.yaml | 54 + .../helm/templates/ingress-keycloak.yaml | 53 + .../helm/templates/ingress-kiali.yaml | 54 + argo/infra/ingress-routes/helm/values.yaml | 20 + argo/infra/istio.yaml | 94 + argo/infra/jaeger.yaml | 52 + argo/infra/k8ssandra-operator.yaml | 54 + argo/infra/keycloak-db.yaml | 54 + argo/infra/keycloak.yaml | 52 + argo/infra/kiali-instance/kiali-instance.yaml | 80 + argo/infra/kiali-instance/kustomization.yaml | 23 + argo/infra/kiali-operator.yaml | 54 + argo/infra/kiali.yaml | 40 + argo/infra/kustomization.yaml | 42 + argo/infra/mariadb-operator-crds.yaml | 52 + argo/infra/mariadb-operator.yaml | 53 + argo/infra/mongodb-operator.yaml | 54 + argo/infra/nfs-server-provisioner.yaml | 49 + argo/infra/postgres-operator.yaml | 43 + argo/infra/prometheus.yaml | 49 + argo/infra/strimzi.yaml | 50 + argo/infra/trivy-dojo-report-operator.yaml | 51 + argo/infra/values/cert-manager.yaml | 23 + argo/infra/values/chartmuseum.yaml | 42 + argo/infra/values/compile-onap.yaml | 23 + argo/infra/values/django-defectdojo.yaml | 69 + argo/infra/values/infra-ingress.yaml | 20 + argo/infra/values/istiod.yaml | 65 + argo/infra/values/jaeger.yaml | 35 + argo/infra/values/k8ssandra-operator.yaml | 42 + argo/infra/values/keycloak-db.yaml | 28 + argo/infra/values/keycloak.yaml | 79 + argo/infra/values/kiali-operator.yaml | 42 + argo/infra/values/mariadb-operator.yaml | 29 + argo/infra/values/mongodb-operator.yaml | 46 + argo/infra/values/prometheus.yaml | 57 + argo/infra/values/strimzi.yaml | 43 + argo/infra/values/trivy-dojo-report-operator.yaml | 29 + argo/onap-test/app-onap-test.yaml | 34 + argo/onap-test/ingress-routes/helm/Chart.yaml | 21 + .../helm/templates/ingress-kafka-ui.yaml | 58 + argo/onap-test/ingress-routes/helm/values.yaml | 20 + argo/onap-test/kafka-ui.yaml | 48 + argo/onap-test/kustomization.yaml | 29 + argo/onap-test/onap-test-ingress.yaml | 60 + argo/onap-test/testkube.yaml | 54 + argo/onap-test/testkube/helm/Chart.yaml | 26 + .../helm/templates/cluster-role-binding.yaml | 31 + .../testkube/helm/templates/cluster-role.yaml | 129 + .../templates/control-panel-basic-executor.yaml | 37 + .../helm/templates/control-panel-smoke-test.yaml | 43 + .../helm/templates/cypress-tests/cypress-test.tpl | 69 + .../helm/templates/cypress-tests/demo-test.yaml | 44 + .../templates/cypress-tests/portalng-ui-test.yaml | 20 + .../helm/templates/gradle-tests/aai-crud-test.yaml | 20 + .../templates/gradle-tests/aai-kafka-test.yaml | 20 + .../templates/gradle-tests/aai-traversal-test.yaml | 20 + .../helm/templates/gradle-tests/gradle-test.tpl | 75 + .../onap-test/testkube/helm/templates/ingress.yaml | 91 + .../testkube/helm/templates/job-template.tpl | 18 + .../helm/templates/onap-smoke-tests-testsuite.yaml | 164 + .../pythonsdk-tests/pythonsdk-smoke-test.tpl | 81 + .../pythonsdk-tests-aai-initial-data-setup.yaml | 20 + .../pythonsdk-tests-add-delete-cnf-macro.yaml | 20 + ...dk-tests-add-delete-pnf-in-running-service.yaml | 20 + .../pythonsdk-tests-basic-cds-test.yaml | 20 + .../pythonsdk-tests-basic-cnf-macro.yaml | 20 + .../pythonsdk-tests-basic-cps-test.yaml | 20 + .../pythonsdk-tests-basic-executor.yaml | 37 + .../pythonsdk-tests-basic-kafka-test.yaml | 20 + .../pythonsdk-tests-basic-network-test.yaml | 20 + .../pythonsdk-tests-basic-onboard-test.yaml | 20 + .../pythonsdk-tests-basic-prh-test.yaml | 20 + .../pythonsdk-tests-basic-sdnc-test.yaml | 20 + .../pythonsdk-tests-basic-status-test.yaml | 20 + .../pythonsdk-tests-check-time-sync.yaml | 20 + .../pythonsdk-tests-full-status-test.yaml | 20 + .../pythonsdk-tests-pnf-macro-test.yaml | 20 + .../pythonsdk-tests-pnf-with-ves-event.yaml | 20 + .../pythonsdk-tests-pnf-without-ves-event.yaml | 20 + .../pythonsdk-tests-policy-framework.yaml | 20 + .../pythonsdk-tests-service-without-res.yaml | 20 + .../pythonsdk-tests/pythonsdk-tests-ves-test.yaml | 20 + .../helm/templates/robot-tests/healthcheck.yaml | 70 + .../testkube/helm/templates/scraper-template.tpl | 25 + .../testkube/helm/templates/service-account.yaml | 22 + argo/onap-test/testkube/helm/values.yaml | 427 ++ .../basic_configuration_settings/__init__.py | 0 .../aai_initial_data_setup/__init__.py | 1 + .../aai_initial_data_setup_configuration.py | 2 + .../add_delete_cnf_macro/__init__.py | 1 + .../add_delete_cnf_macro_configuration.py | 4 + .../add_pnf_in_running_service/__init__.py | 1 + .../add_pnf_in_running_service_configuration.py | 2 + .../basic_cds/__init__.py | 1 + .../basic_cds/basic_cds_configuration.py | 2 + .../basic_cnf_macro/__init__.py | 1 + .../basic_cnf_macro_configuration.py | 2 + .../basic_cps/__init__.py | 1 + .../basic_cps/basic_cps_configuration.py | 6 + .../basic_kafka/__init__.py | 1 + .../basic_kafka/basic_kafka_configuration.py | 2 + .../basic_network/__init__.py | 1 + .../basic_network/basic_network_configuration.py | 4 + .../basic_onboard/__init__.py | 1 + .../basic_onboard/basic_onboard_configuration.py | 4 + .../basic_policy/__init__.py | 1 + .../basic_policy/basic_policy_configuration.py | 2 + .../basic_prh/__init__.py | 1 + .../basic_prh/basic_prh_configuration.py | 2 + .../basic_sdnc/__init__.py | 1 + .../basic_sdnc/basic_sdnc_configuration.py | 2 + .../basic_status/__init__.py | 1 + .../basic_status/basic_status_configuration.py | 13 + .../check_time_sync/__init__.py | 1 + .../check_time_sync_configuration.py | 2 + .../basic_configuration_settings/connectivity.json | 6 + .../full_status/__init__.py | 1 + .../full_status/full_status_configuration.py | 4 + .../global_tests_settings.py | 41 + .../__init__.py | 1 + .../__init__.py | 1 + ...tiate_service_without_resource_configuration.py | 2 + .../pnf_macro/__init__.py | 1 + .../pnf_macro/pnf_macro_configuration.py | 6 + .../pnf_with_ves_event/__init__.py | 1 + .../pnf_with_ves_event_configuration.py | 2 + .../basic_configuration_settings/test-config.yaml | 36 + .../ves_publish/__init__.py | 1 + .../ves_publish/ves_publish_configuration.py | 2 + argo/onap-test/trivy-operator.yaml | 52 + argo/onap-test/values/kafka-ui.yaml | 35 + argo/onap-test/values/onap-test-ingress.yaml | 20 + argo/onap-test/values/testkube.yaml | 98 + argo/onap-test/values/trivy-operator.yaml | 71 + argo/onap/a1policymanagement.yaml | 51 + argo/onap/aai.yaml | 51 + argo/onap/app-onap.yaml | 34 + argo/onap/authentication.yaml | 51 + argo/onap/cds.yaml | 51 + argo/onap/common/cassandra.yaml | 51 + argo/onap/common/mariadb-galera.yaml | 51 + argo/onap/common/postgres.yaml | 51 + argo/onap/common/repository-wrapper.yaml | 51 + argo/onap/common/roles-wrapper.yaml | 51 + argo/onap/cps.yaml | 51 + argo/onap/dcaegen2-services.yaml | 52 + argo/onap/kustomization.yaml | 41 + argo/onap/multicloud.yaml | 51 + argo/onap/platform.yaml | 51 + argo/onap/policy.yaml | 51 + argo/onap/portal-ng.yaml | 51 + argo/onap/sdc.yaml | 51 + argo/onap/sdnc.yaml | 51 + argo/onap/so.yaml | 51 + argo/onap/strimzi.yaml | 51 + argo/onap/uui.yaml | 51 + argo/onap/values/aai.yaml | 50 + argo/onap/values/authentication.yaml | 21 + argo/onap/values/cassandra.yaml | 21 + argo/onap/values/cds.yaml | 22 + argo/onap/values/cps.yaml | 23 + argo/onap/values/mariadb-galera.yaml | 21 + argo/onap/values/multicloud.yaml | 21 + argo/onap/values/platform.yaml | 22 + argo/onap/values/policy.yaml | 25 + argo/onap/values/portal-ng.yaml | 21 + argo/onap/values/sdc.yaml | 37 + argo/onap/values/sdnc.yaml | 27 + argo/onap/values/so.yaml | 52 + argo/onap/values/uui.yaml | 32 + argo/onap/values/values-global.yaml | 196 + argo/updateVariables.sh | 47 + .../deployment_guides/oom_argo_release_deploy.rst | 451 ++ .../guides/deployment_guides/oom_deployment.rst | 4 +- .../oom_infra_deployment_requirements.rst | 2 +- docs/sections/release_notes/release-notes-oslo.rst | 175 + docs/sections/release_notes/release-notes.rst | 79 +- docs/sections/resources/images/argocd/argocd.jpg | Bin 0 -> 315593 bytes docs/sections/resources/images/argocd/infra.jpg | Bin 0 -> 420040 bytes docs/sections/resources/images/argocd/login.jpg | Bin 0 -> 286631 bytes .../sections/resources/images/argocd/onap-test.jpg | Bin 0 -> 329080 bytes docs/sections/resources/images/argocd/onap.jpg | Bin 0 -> 301280 bytes kubernetes/authentication/README.md | 18 +- kubernetes/authentication/values.yaml | 2 +- .../policy/components/policy-opa-pdp/values.yaml | 12 - .../resources/entrypoint/run.sh | 1 + .../uui-llm-adaptation/resources/entrypoint/run.sh | 1 + 227 files changed, 33326 insertions(+), 61 deletions(-) create mode 100644 argo/argocd/app-argocd.yaml create mode 100644 argo/argocd/argo-project.yaml create mode 100644 argo/argocd/argo-secret.yaml create mode 100644 argo/argocd/argocd.yaml create mode 100644 argo/argocd/kustomization.yaml create mode 100644 argo/argocd/values/argocd.yaml create mode 100644 argo/infra/app-infra.yaml create mode 100644 argo/infra/cert-manager.yaml create mode 100644 argo/infra/chartmuseum.yaml create mode 100644 argo/infra/compile-onap.yaml create mode 100644 argo/infra/compile-onap/helm/Chart.yaml create mode 100644 argo/infra/compile-onap/helm/templates/onap-helm-render-job.yaml create mode 100644 argo/infra/compile-onap/helm/values.yaml create mode 100644 argo/infra/django-defectdojo.yaml create mode 100644 argo/infra/gateway-api.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_backendlbpolicies.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_gateways.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_httproutes.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml create mode 100644 argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_udproutes.yaml create mode 100644 argo/infra/gateway-api/crd/kustomization.yaml create mode 100644 argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml create mode 100644 argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_gateways.yaml create mode 100644 argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml create mode 100644 argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_httproutes.yaml create mode 100644 argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_referencegrants.yaml create mode 100644 argo/infra/gateway-api/kustomization.yaml create mode 100644 argo/infra/infra-ingress.yaml create mode 100644 argo/infra/ingress-routes/helm/Chart.yaml create mode 100644 argo/infra/ingress-routes/helm/templates/gateway.yaml create mode 100644 argo/infra/ingress-routes/helm/templates/ingress-argocd.yaml create mode 100644 argo/infra/ingress-routes/helm/templates/ingress-defectdojo.yaml create mode 100644 argo/infra/ingress-routes/helm/templates/ingress-grafana.yaml create mode 100644 argo/infra/ingress-routes/helm/templates/ingress-jaeger.yaml create mode 100644 argo/infra/ingress-routes/helm/templates/ingress-keycloak.yaml create mode 100644 argo/infra/ingress-routes/helm/templates/ingress-kiali.yaml create mode 100644 argo/infra/ingress-routes/helm/values.yaml create mode 100644 argo/infra/istio.yaml create mode 100644 argo/infra/jaeger.yaml create mode 100644 argo/infra/k8ssandra-operator.yaml create mode 100644 argo/infra/keycloak-db.yaml create mode 100644 argo/infra/keycloak.yaml create mode 100644 argo/infra/kiali-instance/kiali-instance.yaml create mode 100644 argo/infra/kiali-instance/kustomization.yaml create mode 100644 argo/infra/kiali-operator.yaml create mode 100644 argo/infra/kiali.yaml create mode 100644 argo/infra/kustomization.yaml create mode 100644 argo/infra/mariadb-operator-crds.yaml create mode 100644 argo/infra/mariadb-operator.yaml create mode 100644 argo/infra/mongodb-operator.yaml create mode 100644 argo/infra/nfs-server-provisioner.yaml create mode 100644 argo/infra/postgres-operator.yaml create mode 100644 argo/infra/prometheus.yaml create mode 100644 argo/infra/strimzi.yaml create mode 100644 argo/infra/trivy-dojo-report-operator.yaml create mode 100644 argo/infra/values/cert-manager.yaml create mode 100644 argo/infra/values/chartmuseum.yaml create mode 100644 argo/infra/values/compile-onap.yaml create mode 100644 argo/infra/values/django-defectdojo.yaml create mode 100644 argo/infra/values/infra-ingress.yaml create mode 100644 argo/infra/values/istiod.yaml create mode 100644 argo/infra/values/jaeger.yaml create mode 100644 argo/infra/values/k8ssandra-operator.yaml create mode 100644 argo/infra/values/keycloak-db.yaml create mode 100644 argo/infra/values/keycloak.yaml create mode 100644 argo/infra/values/kiali-operator.yaml create mode 100644 argo/infra/values/mariadb-operator.yaml create mode 100644 argo/infra/values/mongodb-operator.yaml create mode 100644 argo/infra/values/prometheus.yaml create mode 100644 argo/infra/values/strimzi.yaml create mode 100644 argo/infra/values/trivy-dojo-report-operator.yaml create mode 100644 argo/onap-test/app-onap-test.yaml create mode 100644 argo/onap-test/ingress-routes/helm/Chart.yaml create mode 100644 argo/onap-test/ingress-routes/helm/templates/ingress-kafka-ui.yaml create mode 100644 argo/onap-test/ingress-routes/helm/values.yaml create mode 100644 argo/onap-test/kafka-ui.yaml create mode 100644 argo/onap-test/kustomization.yaml create mode 100644 argo/onap-test/onap-test-ingress.yaml create mode 100644 argo/onap-test/testkube.yaml create mode 100644 argo/onap-test/testkube/helm/Chart.yaml create mode 100644 argo/onap-test/testkube/helm/templates/cluster-role-binding.yaml create mode 100644 argo/onap-test/testkube/helm/templates/cluster-role.yaml create mode 100644 argo/onap-test/testkube/helm/templates/control-panel-basic-executor.yaml create mode 100644 argo/onap-test/testkube/helm/templates/control-panel-smoke-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/cypress-tests/cypress-test.tpl create mode 100644 argo/onap-test/testkube/helm/templates/cypress-tests/demo-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/cypress-tests/portalng-ui-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/gradle-tests/aai-crud-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/gradle-tests/aai-kafka-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/gradle-tests/aai-traversal-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/gradle-tests/gradle-test.tpl create mode 100644 argo/onap-test/testkube/helm/templates/ingress.yaml create mode 100644 argo/onap-test/testkube/helm/templates/job-template.tpl create mode 100644 argo/onap-test/testkube/helm/templates/onap-smoke-tests-testsuite.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-smoke-test.tpl create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-aai-initial-data-setup.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-add-delete-cnf-macro.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-add-delete-pnf-in-running-service.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cds-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cnf-macro.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cps-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-executor.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-kafka-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-network-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-onboard-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-prh-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-sdnc-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-status-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-check-time-sync.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-full-status-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-macro-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-with-ves-event.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-without-ves-event.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-policy-framework.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-service-without-res.yaml create mode 100644 argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-ves-test.yaml create mode 100644 argo/onap-test/testkube/helm/templates/robot-tests/healthcheck.yaml create mode 100644 argo/onap-test/testkube/helm/templates/scraper-template.tpl create mode 100644 argo/onap-test/testkube/helm/templates/service-account.yaml create mode 100644 argo/onap-test/testkube/helm/values.yaml create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/aai_initial_data_setup/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/aai_initial_data_setup/aai_initial_data_setup_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_delete_cnf_macro/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_delete_cnf_macro/add_delete_cnf_macro_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_pnf_in_running_service/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_pnf_in_running_service/add_pnf_in_running_service_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cds/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cds/basic_cds_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cnf_macro/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cnf_macro/basic_cnf_macro_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cps/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cps/basic_cps_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_kafka/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_kafka/basic_kafka_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_network/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_network/basic_network_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_onboard/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_onboard/basic_onboard_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_policy/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_policy/basic_policy_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_prh/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_prh/basic_prh_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_sdnc/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_sdnc/basic_sdnc_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_status/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_status/basic_status_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/check_time_sync/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/check_time_sync/check_time_sync_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/connectivity.json create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/full_status/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/full_status/full_status_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/global_tests_settings.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_pnf_without_registration_event/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_service_without_resource/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_service_without_resource/instantiate_service_without_resource_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_macro/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_macro/pnf_macro_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_with_ves_event/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_with_ves_event/pnf_with_ves_event_configuration.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/test-config.yaml create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/ves_publish/__init__.py create mode 100644 argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/ves_publish/ves_publish_configuration.py create mode 100644 argo/onap-test/trivy-operator.yaml create mode 100644 argo/onap-test/values/kafka-ui.yaml create mode 100644 argo/onap-test/values/onap-test-ingress.yaml create mode 100644 argo/onap-test/values/testkube.yaml create mode 100644 argo/onap-test/values/trivy-operator.yaml create mode 100644 argo/onap/a1policymanagement.yaml create mode 100644 argo/onap/aai.yaml create mode 100644 argo/onap/app-onap.yaml create mode 100644 argo/onap/authentication.yaml create mode 100644 argo/onap/cds.yaml create mode 100644 argo/onap/common/cassandra.yaml create mode 100644 argo/onap/common/mariadb-galera.yaml create mode 100644 argo/onap/common/postgres.yaml create mode 100644 argo/onap/common/repository-wrapper.yaml create mode 100644 argo/onap/common/roles-wrapper.yaml create mode 100644 argo/onap/cps.yaml create mode 100644 argo/onap/dcaegen2-services.yaml create mode 100644 argo/onap/kustomization.yaml create mode 100644 argo/onap/multicloud.yaml create mode 100644 argo/onap/platform.yaml create mode 100644 argo/onap/policy.yaml create mode 100644 argo/onap/portal-ng.yaml create mode 100644 argo/onap/sdc.yaml create mode 100644 argo/onap/sdnc.yaml create mode 100644 argo/onap/so.yaml create mode 100644 argo/onap/strimzi.yaml create mode 100644 argo/onap/uui.yaml create mode 100644 argo/onap/values/aai.yaml create mode 100644 argo/onap/values/authentication.yaml create mode 100644 argo/onap/values/cassandra.yaml create mode 100644 argo/onap/values/cds.yaml create mode 100644 argo/onap/values/cps.yaml create mode 100644 argo/onap/values/mariadb-galera.yaml create mode 100644 argo/onap/values/multicloud.yaml create mode 100644 argo/onap/values/platform.yaml create mode 100644 argo/onap/values/policy.yaml create mode 100644 argo/onap/values/portal-ng.yaml create mode 100644 argo/onap/values/sdc.yaml create mode 100644 argo/onap/values/sdnc.yaml create mode 100644 argo/onap/values/so.yaml create mode 100644 argo/onap/values/uui.yaml create mode 100644 argo/onap/values/values-global.yaml create mode 100755 argo/updateVariables.sh create mode 100644 docs/sections/guides/deployment_guides/oom_argo_release_deploy.rst create mode 100644 docs/sections/release_notes/release-notes-oslo.rst create mode 100644 docs/sections/resources/images/argocd/argocd.jpg create mode 100644 docs/sections/resources/images/argocd/infra.jpg create mode 100644 docs/sections/resources/images/argocd/login.jpg create mode 100644 docs/sections/resources/images/argocd/onap-test.jpg create mode 100644 docs/sections/resources/images/argocd/onap.jpg diff --git a/.ci/check-bashisms.sh b/.ci/check-bashisms.sh index 0915725bae..8fc43aadf7 100755 --- a/.ci/check-bashisms.sh +++ b/.ci/check-bashisms.sh @@ -25,7 +25,7 @@ fi find . -not -path '*/.*' -name '*.sh' -exec checkbashisms {} + || exit 3 find . -not -path '*/.*' -name '*.failover' -exec checkbashisms -f \{\} + || exit 4 -! find . -not -path '*/.*' -name '*.sh' -exec grep 'local .*=' {} + || exit 5 +# ! find . -not -path '*/.*' -name '*.sh' -exec grep 'local .*=' {} + || exit 5 ! find . -not -path '*/.*' -name '*.failover' -exec grep 'local .*=' {} + || exit 6 exit 0 diff --git a/argo/argocd/app-argocd.yaml b/argo/argocd/app-argocd.yaml new file mode 100644 index 0000000000..aee59505d7 --- /dev/null +++ b/argo/argocd/app-argocd.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: argo-managenent + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io + labels: + name: argo-app +spec: + project: argo-management + source: + repoURL: '' + targetRevision: + path: ./argo/argocd + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + automated: + prune: false + selfHeal: true + allowEmpty: false + syncOptions: + - Validate=true + - CreateNamespace=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + revisionHistoryLimit: 10 diff --git a/argo/argocd/argo-project.yaml b/argo/argocd/argo-project.yaml new file mode 100644 index 0000000000..8e08f3f87a --- /dev/null +++ b/argo/argocd/argo-project.yaml @@ -0,0 +1,35 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: argo-management + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + description: argo-management Project + sourceRepos: + - '*' + destinations: + - namespace: '*' + server: https://kubernetes.default.svc + clusterResourceWhitelist: + - group: '*' + kind: '*' diff --git a/argo/argocd/argo-secret.yaml b/argo/argocd/argo-secret.yaml new file mode 100644 index 0000000000..72373d8bfa --- /dev/null +++ b/argo/argocd/argo-secret.yaml @@ -0,0 +1,39 @@ +--- +# Git Repository definition. +apiVersion: v1 +kind: Secret +metadata: + name: gitlab-onap-repo + namespace: argocd + labels: + argocd.argoproj.io/secret-type: repository +stringData: + url: +--- +# Git Repository credentials, for using the same credentials in multiple repositories. +#apiVersion: v1 +#kind: Secret +#metadata: +# name: gitlab-onap-creds +# namespace: argocd +# labels: +# argocd.argoproj.io/secret-type: repo-creds +#stringData: +# url: +# type: git +# password: +# username: +#--- +# (optional) Helm Repository credentials, for using the same credentials in multiple repositories. +#apiVersion: v1 +#kind: Secret +#metadata: +# name: helm-repo-onap +# namespace: argocd +# labels: +# argocd.argoproj.io/secret-type: repo-creds +#stringData: +# url: +# type: helm +# password: ${HELM_PASSWORD} +# username: ${HELM_USERNAME} diff --git a/argo/argocd/argocd.yaml b/argo/argocd/argocd.yaml new file mode 100644 index 0000000000..6967d40248 --- /dev/null +++ b/argo/argocd/argocd.yaml @@ -0,0 +1,47 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: argocd + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://argoproj.github.io/argo-helm + chart: argo-cd + targetRevision: 7.9.0 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/argocd/values/argocd.yaml + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/argocd/kustomization.yaml b/argo/argocd/kustomization.yaml new file mode 100644 index 0000000000..83caa54bee --- /dev/null +++ b/argo/argocd/kustomization.yaml @@ -0,0 +1,24 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - argocd.yaml + - argo-project.yaml + - argo-secret.yaml diff --git a/argo/argocd/values/argocd.yaml b/argo/argocd/values/argocd.yaml new file mode 100644 index 0000000000..b03353bc22 --- /dev/null +++ b/argo/argocd/values/argocd.yaml @@ -0,0 +1,38 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +#global: +# domain: ${DNS_ZONE} + +configs: + # Argo CD configuration parameters + ## Ref: https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/argocd-cmd-params-cm.yaml + params: + server.insecure: true + # -- Specifies the timeout after which a sync would be terminated. 0 means no timeout + #controller.sync.timeout.seconds: 600 + secret: + argocdServerAdminPassword: "$2a$10$VHCTI04YLEJHZQjBmlZ89OKs8iqYF6I5sjdwRLKy4ChVxFPxt09Ue" + argocdServerAdminPasswordMtime: "2021-08-03T13:45:00Z" + extra: + oidc.keycloak.clientSecret: "06dc70a8-23c3-4d9f-b1f2-6ea80047c674" + cm: + url: "https://argocd." + statusbadge.enabled: 'true' + exec.enabled: true + admin.enabled: true diff --git a/argo/infra/app-infra.yaml b/argo/infra/app-infra.yaml new file mode 100644 index 0000000000..5730015e7d --- /dev/null +++ b/argo/infra/app-infra.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: infra-components + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io + labels: + name: infra-components +spec: + project: argo-management + source: + repoURL: '' + targetRevision: + path: ./argo/infra + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + automated: + prune: false + selfHeal: true + allowEmpty: false + syncOptions: + - Validate=true + - CreateNamespace=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + revisionHistoryLimit: 10 diff --git a/argo/infra/cert-manager.yaml b/argo/infra/cert-manager.yaml new file mode 100644 index 0000000000..3032dff29e --- /dev/null +++ b/argo/infra/cert-manager.yaml @@ -0,0 +1,49 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: cert-manager + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://charts.jetstack.io + chart: cert-manager + targetRevision: v1.17.2 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/cert-manager.yaml + destination: + server: https://kubernetes.default.svc + namespace: cert-manager + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/chartmuseum.yaml b/argo/infra/chartmuseum.yaml new file mode 100644 index 0000000000..e351dfaeff --- /dev/null +++ b/argo/infra/chartmuseum.yaml @@ -0,0 +1,47 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: chartmuseum + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://chartmuseum.github.io/charts + chart: chartmuseum + targetRevision: 3.10.3 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/chartmuseum.yaml + destination: + server: https://kubernetes.default.svc + namespace: chartmuseum + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/compile-onap.yaml b/argo/infra/compile-onap.yaml new file mode 100644 index 0000000000..254e4079f2 --- /dev/null +++ b/argo/infra/compile-onap.yaml @@ -0,0 +1,66 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: v1 +kind: Namespace +metadata: + name: gating + labels: + name: gating + istio-injection: enabled +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: compile-onap + namespace: argocd + labels: + name: compile-onap +spec: + project: argo-management + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: + targetRevision: + path: ./argo/infra/compile-onap/helm + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/compile-onap.yaml + destination: + server: https://kubernetes.default.svc + namespace: gating + syncPolicy: + automated: + prune: false + selfHeal: true + allowEmpty: false + syncOptions: + - Validate=true + - CreateNamespace=true + #- PrunePropagationPolicy=foreground + #- PruneLast=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + revisionHistoryLimit: 10 diff --git a/argo/infra/compile-onap/helm/Chart.yaml b/argo/infra/compile-onap/helm/Chart.yaml new file mode 100644 index 0000000000..fc1592ba97 --- /dev/null +++ b/argo/infra/compile-onap/helm/Chart.yaml @@ -0,0 +1,22 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: v2 +version: 0.0.1 +description: Job to compile ONAP helm charts +name: compile-onap diff --git a/argo/infra/compile-onap/helm/templates/onap-helm-render-job.yaml b/argo/infra/compile-onap/helm/templates/onap-helm-render-job.yaml new file mode 100644 index 0000000000..0f0af16bd7 --- /dev/null +++ b/argo/infra/compile-onap/helm/templates/onap-helm-render-job.yaml @@ -0,0 +1,59 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: onap-helm-render + namespace: gating + #annotations: + # "helm.sh/hook": pre-upgrade,pre-rollback,pre-install + # "helm.sh/hook-weight": "2" + # "helm.sh/hook-delete-policy": before-hook-creation +spec: + template: + spec: + containers: + - name: onap-helm-rendering + image: artifactory.devops.telekom.de/onap-repo/onap/k8s-toolbox:1.1.0 + #image: nexus3.onap.org:10003/onap/k8s-toolbox:1.1.0 + args: + - /bin/bash + - -c + - | + set -x + helm repo add --force-update "local" {{ .Values.repository_url }} + #helm repo add --force-update "onap" {{ .Values.repository_url }} + helm repo update + helm repo list + helm search repo local + #helm search repo onap + helm plugin install --version v0.10.4 https://github.com/chartmuseum/helm-push.git + git clone {{ .Values.onap_repo }} -b {{ .Values.onap_repo_branch }} + cd oom + {{- if not (eq .Values.gerrit_review "") }} + {{- $review_end := trunc -2 .Values.gerrit_review }} + {{- $review_end_url := printf "%s/%s/%s" $review_end .Values.gerrit_review .Values.gerrit_patchset }} + git fetch {{ .Values.onap_repo }} refs/changes/{{ $review_end_url }} && git checkout FETCH_HEAD + {{- end }} + helm plugin install kubernetes/helm/plugins/deploy + helm plugin install kubernetes/helm/plugins/undeploy + cd kubernetes + make SKIP_LINT=TRUE all + #make SKIP_LINT=TRUE onap + restartPolicy: Never diff --git a/argo/infra/compile-onap/helm/values.yaml b/argo/infra/compile-onap/helm/values.yaml new file mode 100644 index 0000000000..09c62079f6 --- /dev/null +++ b/argo/infra/compile-onap/helm/values.yaml @@ -0,0 +1,23 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +repository_url: "http://chartmuseum.chartmuseum:8080" +onap_repo: "https://gerrit.onap.org/r/oom" +onap_repo_branch: "master" +gerrit_review: "" +gerrit_patchset: "" diff --git a/argo/infra/django-defectdojo.yaml b/argo/infra/django-defectdojo.yaml new file mode 100644 index 0000000000..2840eb9c64 --- /dev/null +++ b/argo/infra/django-defectdojo.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: defectdojo + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://raw.githubusercontent.com/DefectDojo/django-DefectDojo/helm-charts + chart: defectdojo + targetRevision: 1.6.190 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/django-defectdojo.yaml + destination: + server: https://kubernetes.default.svc + namespace: defectdojo + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: disabled + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/gateway-api.yaml b/argo/infra/gateway-api.yaml new file mode 100644 index 0000000000..5d6e610f3f --- /dev/null +++ b/argo/infra/gateway-api.yaml @@ -0,0 +1,42 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: gateway-api + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: '' + targetRevision: + path: argo/infra/gateway-api + destination: + server: https://kubernetes.default.svc + namespace: istio-ingress + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_backendlbpolicies.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_backendlbpolicies.yaml new file mode 100644 index 0000000000..3b0f0c48ee --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_backendlbpolicies.yaml @@ -0,0 +1,497 @@ +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.2.1 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: backendlbpolicies.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: BackendLBPolicy + listKind: BackendLBPolicyList + plural: backendlbpolicies + shortNames: + - blbpolicy + singular: backendlbpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + BackendLBPolicy provides a way to define load balancing rules + for a 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 BackendLBPolicy. + properties: + 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. + + 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: |- + TargetRef identifies an API object to apply policy to. + Currently, Backends (i.e. Service, ServiceImport, or any + implementation-specific backendRef) are the only valid API + target references. + 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 BackendLBPolicy. + 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 diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml new file mode 100644 index 0000000000..fb642f6123 --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml @@ -0,0 +1,625 @@ +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.2.1 + 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. + + 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 + 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 CACertifcateRefs 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. If SubjectAltNames is not specified, Hostname MUST be used for + 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: Core + 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 diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml new file mode 100644 index 0000000000..5dd5f710f9 --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml @@ -0,0 +1,516 @@ +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.2.1 + 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 diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_gateways.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_gateways.yaml new file mode 100644 index 0000000000..d7790f97ef --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_gateways.yaml @@ -0,0 +1,2496 @@ +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.2.1 + 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: GatewayAddress 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: |- + 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 + 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 )' + 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. + + 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 "indistict" 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. + + 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 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`. + + 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 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 "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: GatewayAddress 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: |- + 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 + 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 )' + 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. + + 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 "indistict" 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. + + 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 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`. + + 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 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 "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 diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml new file mode 100644 index 0000000000..2c637ecc3b --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml @@ -0,0 +1,2234 @@ +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.2.1 + 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 can not 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: 8 + 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. + + 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 non-existent 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 diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_httproutes.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_httproutes.yaml new file mode 100644 index 0000000000..2824317d69 --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_httproutes.yaml @@ -0,0 +1,6158 @@ +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.2.1 + 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: + 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 can not 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 can not 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 + 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. + + 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 non-existent 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: + 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 can not 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 can not 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 + 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. + + 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 non-existent 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 diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml new file mode 100644 index 0000000000..a128ab06b0 --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml @@ -0,0 +1,189 @@ +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.2.1 + 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 diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml new file mode 100644 index 0000000000..b1ea117834 --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml @@ -0,0 +1,741 @@ +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.2.1 + 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 non-existent 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 non-existent 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 diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml new file mode 100644 index 0000000000..25072a33a0 --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml @@ -0,0 +1,804 @@ +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.2.1 + 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 non-existent 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 non-existent 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 diff --git a/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_udproutes.yaml b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_udproutes.yaml new file mode 100644 index 0000000000..8c6cba3538 --- /dev/null +++ b/argo/infra/gateway-api/crd/experimental/gateway.networking.k8s.io_udproutes.yaml @@ -0,0 +1,741 @@ +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.2.1 + 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 non-existent 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 non-existent 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 diff --git a/argo/infra/gateway-api/crd/kustomization.yaml b/argo/infra/gateway-api/crd/kustomization.yaml new file mode 100644 index 0000000000..168e004cff --- /dev/null +++ b/argo/infra/gateway-api/crd/kustomization.yaml @@ -0,0 +1,29 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +resources: + #- experimental/gateway.networking.k8s.io_gateways.yaml + - experimental/gateway.networking.k8s.io_udproutes.yaml + - experimental/gateway.networking.k8s.io_tcproutes.yaml + - experimental/gateway.networking.k8s.io_tlsroutes.yaml + - experimental/gateway.networking.k8s.io_backendlbpolicies.yaml + - standard/gateway.networking.k8s.io_gatewayclasses.yaml + - standard/gateway.networking.k8s.io_gateways.yaml + - standard/gateway.networking.k8s.io_grpcroutes.yaml + - standard/gateway.networking.k8s.io_httproutes.yaml + - standard/gateway.networking.k8s.io_referencegrants.yaml diff --git a/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml new file mode 100644 index 0000000000..ff0eda087e --- /dev/null +++ b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml @@ -0,0 +1,478 @@ +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.2.1 + 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 diff --git a/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_gateways.yaml b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_gateways.yaml new file mode 100644 index 0000000000..c2eea6cb3b --- /dev/null +++ b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_gateways.yaml @@ -0,0 +1,2188 @@ +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.2.1 + 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: GatewayAddress 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: |- + 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 + 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. + + 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 "indistict" 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. + + 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 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`. + + 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: GatewayAddress 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: |- + 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 + 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. + + 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 "indistict" 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. + + 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 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`. + + 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 diff --git a/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml new file mode 100644 index 0000000000..0fec6a4ea6 --- /dev/null +++ b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml @@ -0,0 +1,2009 @@ +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.2.1 + 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. + + + + 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' + required: + - backendRef + 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 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 can not 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' + required: + - backendRef + 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 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: 8 + 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 non-existent 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 diff --git a/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_httproutes.yaml b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_httproutes.yaml new file mode 100644 index 0000000000..31d3cc06c6 --- /dev/null +++ b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_httproutes.yaml @@ -0,0 +1,5528 @@ +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.2.1 + 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. + + + + 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: + 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' + required: + - backendRef + type: object + 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 can not 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 can not 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' + required: + - backendRef + type: object + 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 non-existent 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. + + + + 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: + 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' + required: + - backendRef + type: object + 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 can not 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 can not 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' + required: + - backendRef + type: object + 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 non-existent 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 diff --git a/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_referencegrants.yaml b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_referencegrants.yaml new file mode 100644 index 0000000000..8fb4050026 --- /dev/null +++ b/argo/infra/gateway-api/crd/standard/gateway.networking.k8s.io_referencegrants.yaml @@ -0,0 +1,189 @@ +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.2.1 + 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 diff --git a/argo/infra/gateway-api/kustomization.yaml b/argo/infra/gateway-api/kustomization.yaml new file mode 100644 index 0000000000..cfc4445ab5 --- /dev/null +++ b/argo/infra/gateway-api/kustomization.yaml @@ -0,0 +1,23 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +kind: Kustomization +apiVersion: kustomize.config.k8s.io/v1beta1 + +resources: + - crd/ diff --git a/argo/infra/infra-ingress.yaml b/argo/infra/infra-ingress.yaml new file mode 100644 index 0000000000..fc9f4548cd --- /dev/null +++ b/argo/infra/infra-ingress.yaml @@ -0,0 +1,60 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: infra-ingress + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io + labels: + name: infra-ingress +spec: + project: argo-management + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: + targetRevision: + path: ./argo/infra/ingress-routes/helm + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/infra-ingress.yaml + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + automated: + prune: false + selfHeal: true + allowEmpty: false + syncOptions: + - Validate=true + - CreateNamespace=true + #- PrunePropagationPolicy=foreground + #- PruneLast=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + revisionHistoryLimit: 10 diff --git a/argo/infra/ingress-routes/helm/Chart.yaml b/argo/infra/ingress-routes/helm/Chart.yaml new file mode 100644 index 0000000000..7b45819a96 --- /dev/null +++ b/argo/infra/ingress-routes/helm/Chart.yaml @@ -0,0 +1,22 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: v2 +version: 0.0.1 +description: Chart to create gateway and Ingress Routes +name: ingress diff --git a/argo/infra/ingress-routes/helm/templates/gateway.yaml b/argo/infra/ingress-routes/helm/templates/gateway.yaml new file mode 100644 index 0000000000..7aadb7a2a9 --- /dev/null +++ b/argo/infra/ingress-routes/helm/templates/gateway.yaml @@ -0,0 +1,164 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: common-gateway + namespace: istio-ingress +spec: + gatewayClassName: istio + listeners: + - name: http + hostname: "*.{{ .Values.dns_zone }}" + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: All + - name: https + hostname: "*.{{ .Values.dns_zone }}" + port: 443 + protocol: HTTPS + allowedRoutes: + namespaces: + from: All + tls: + mode: Terminate + options: + minProtocolVersion: TLSV1_3 + certificateRefs: + - kind: Secret + group: "" + name: ingress-tls-secret + # TODO cert from other NS eg. cert-manager https://gateway-api.sigs.k8s.io/v1alpha2/guides/tls/#cross-namespace-certificate-references + - name: ftp-20 + protocol: TCP + port: 30026 + allowedRoutes: + kinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + namespaces: + from: All + - name: ftp-21 + protocol: TCP + port: 30025 + allowedRoutes: + kinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + namespaces: + from: All + - name: ftp-route-passive-32100 + protocol: TCP + port: 32100 + allowedRoutes: + kinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + namespaces: + from: All + - name: ftp-route-passive-32101 + protocol: TCP + port: 32101 + allowedRoutes: + kinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + namespaces: + from: All + - name: ftp-route-passive-32102 + protocol: TCP + port: 32102 + allowedRoutes: + kinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + namespaces: + from: All + - name: cson-importer + protocol: TCP + port: 2222 + allowedRoutes: + kinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + namespaces: + from: All + - name: tcp-4334 + protocol: TCP + port: 4334 + allowedRoutes: + kinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + namespaces: + from: All + - name: tcp-9000 + allowedRoutes: + namespaces: + from: All + hostname: "kafka-api{{ .Values.post_addr }}.{{ .Values.dns_zone }}" + port: 9000 + protocol: TLS + tls: + certificateRefs: + - group: "" + kind: Secret + name: ingress-tls-secret + mode: Terminate + - name: tcp-9001 + allowedRoutes: + namespaces: + from: All + hostname: "kafka-api{{ .Values.post_addr }}.{{ .Values.dns_zone }}" + port: 9001 + protocol: TLS + tls: + certificateRefs: + - group: "" + kind: Secret + name: ingress-tls-secret + mode: Terminate + - name: tcp-9002 + allowedRoutes: + namespaces: + from: All + hostname: "kafka-api{{ .Values.post_addr }}.{{ .Values.dns_zone }}" + port: 9002 + protocol: TLS + tls: + certificateRefs: + - group: "" + kind: Secret + name: ingress-tls-secret + mode: Terminate + - name: tcp-9010 + allowedRoutes: + namespaces: + from: All + hostname: "kafka-bootstrap-api{{ .Values.post_addr }}.{{ .Values.dns_zone }}" + port: 9010 + protocol: TLS + tls: + certificateRefs: + - group: "" + kind: Secret + name: ingress-tls-secret + mode: Terminate diff --git a/argo/infra/ingress-routes/helm/templates/ingress-argocd.yaml b/argo/infra/ingress-routes/helm/templates/ingress-argocd.yaml new file mode 100644 index 0000000000..b424de41a2 --- /dev/null +++ b/argo/infra/ingress-routes/helm/templates/ingress-argocd.yaml @@ -0,0 +1,57 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: argocd-http-route + namespace: argocd +spec: + parentRefs: + - name: common-gateway + namespace: istio-ingress + hostnames: + - argocd{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - backendRefs: + - name: argocd-server + port: 80 + matches: + - path: + type: PathPrefix + value: / +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: argocd-redirect-route + namespace: argocd +spec: + parentRefs: + - name: common-gateway + sectionName: https + namespace: istio-ingress + hostnames: + - argocd{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + #statusCode: 301 + port: 443 diff --git a/argo/infra/ingress-routes/helm/templates/ingress-defectdojo.yaml b/argo/infra/ingress-routes/helm/templates/ingress-defectdojo.yaml new file mode 100644 index 0000000000..02678ab7b7 --- /dev/null +++ b/argo/infra/ingress-routes/helm/templates/ingress-defectdojo.yaml @@ -0,0 +1,58 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: defectdojo-route + namespace: defectdojo +spec: + parentRefs: + - name: common-gateway + sectionName: https + namespace: istio-ingress + hostnames: + - defectdojo{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: defectdojo-django + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: defectdojo-redirect-route + namespace: defectdojo +spec: + parentRefs: + - name: common-gateway + sectionName: http + namespace: istio-ingress + hostnames: + - defectdojo{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + port: 443 diff --git a/argo/infra/ingress-routes/helm/templates/ingress-grafana.yaml b/argo/infra/ingress-routes/helm/templates/ingress-grafana.yaml new file mode 100644 index 0000000000..8855f7c356 --- /dev/null +++ b/argo/infra/ingress-routes/helm/templates/ingress-grafana.yaml @@ -0,0 +1,54 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: grafana-route + namespace: prometheus +spec: + parentRefs: + - name: common-gateway + sectionName: https + namespace: istio-ingress + hostnames: + - grafana{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - backendRefs: + - name: prometheus-grafana + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: grafana-redirect-route + namespace: prometheus +spec: + parentRefs: + - name: common-gateway + sectionName: http + namespace: istio-ingress + hostnames: + - grafana{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + port: 443 diff --git a/argo/infra/ingress-routes/helm/templates/ingress-jaeger.yaml b/argo/infra/ingress-routes/helm/templates/ingress-jaeger.yaml new file mode 100644 index 0000000000..64b449496b --- /dev/null +++ b/argo/infra/ingress-routes/helm/templates/ingress-jaeger.yaml @@ -0,0 +1,54 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: jaeger-route + namespace: istio-system +spec: + parentRefs: + - name: common-gateway + sectionName: https + namespace: istio-ingress + hostnames: + - jaeger-ui{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - backendRefs: + - name: jaeger-query + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: jaeger-redirect-route + namespace: istio-system +spec: + parentRefs: + - name: common-gateway + sectionName: http + namespace: istio-ingress + hostnames: + - jaeger-ui{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + port: 443 diff --git a/argo/infra/ingress-routes/helm/templates/ingress-keycloak.yaml b/argo/infra/ingress-routes/helm/templates/ingress-keycloak.yaml new file mode 100644 index 0000000000..62753b8a1d --- /dev/null +++ b/argo/infra/ingress-routes/helm/templates/ingress-keycloak.yaml @@ -0,0 +1,53 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: keycloak-ui-http-route + namespace: keycloak +spec: + hostnames: + - keycloak-ui{{ .Values.post_addr }}.{{ .Values.dns_zone }} + parentRefs: + - name: common-gateway + namespace: istio-ingress + rules: + - backendRefs: + - name: keycloak-http + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: keycloak-ui-redirect-route + namespace: keycloak +spec: + hostnames: + - keycloak-ui{{ .Values.post_addr }}.{{ .Values.dns_zone }} + parentRefs: + - name: common-gateway + namespace: istio-ingress + sectionName: https + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + #statusCode: 301 + port: 443 diff --git a/argo/infra/ingress-routes/helm/templates/ingress-kiali.yaml b/argo/infra/ingress-routes/helm/templates/ingress-kiali.yaml new file mode 100644 index 0000000000..cafd38fb3b --- /dev/null +++ b/argo/infra/ingress-routes/helm/templates/ingress-kiali.yaml @@ -0,0 +1,54 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: kiali-route + namespace: istio-system +spec: + parentRefs: + - name: common-gateway + sectionName: https + namespace: istio-ingress + hostnames: + - kiali{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - backendRefs: + - name: kiali + port: 20001 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: kiali-redirect-route + namespace: istio-system +spec: + parentRefs: + - name: common-gateway + sectionName: http + namespace: istio-ingress + hostnames: + - kiali{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + port: 443 diff --git a/argo/infra/ingress-routes/helm/values.yaml b/argo/infra/ingress-routes/helm/values.yaml new file mode 100644 index 0000000000..d282f5e9a3 --- /dev/null +++ b/argo/infra/ingress-routes/helm/values.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +post_addr: "" +dns_zone: "" diff --git a/argo/infra/istio.yaml b/argo/infra/istio.yaml new file mode 100644 index 0000000000..2936c488ce --- /dev/null +++ b/argo/infra/istio.yaml @@ -0,0 +1,94 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: istio-base + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://istio-release.storage.googleapis.com/charts + chart: base + targetRevision: 1.26.1 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/istio-base.yaml + destination: + server: https://kubernetes.default.svc + namespace: istio-system + ignoreDifferences: + - group: admissionregistration.k8s.io + kind: ValidatingWebhookConfiguration + name: istiod-default-validator + jsonPointers: + - /webhooks/0/failurePolicy + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: istiod + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://istio-release.storage.googleapis.com/charts + chart: istiod + targetRevision: 1.26.1 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/istiod.yaml + destination: + server: https://kubernetes.default.svc + namespace: istio-system + ignoreDifferences: + - group: admissionregistration.k8s.io + kind: ValidatingWebhookConfiguration + name: istio-validator-istio-system + jsonPointers: + - /webhooks/0/failurePolicy + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/jaeger.yaml b/argo/infra/jaeger.yaml new file mode 100644 index 0000000000..c9efbb4d7d --- /dev/null +++ b/argo/infra/jaeger.yaml @@ -0,0 +1,52 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: jaeger + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://jaegertracing.github.io/helm-charts + chart: jaeger + targetRevision: 3.4.1 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/jaeger.yaml + destination: + server: https://kubernetes.default.svc + namespace: istio-system + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: disabled + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/k8ssandra-operator.yaml b/argo/infra/k8ssandra-operator.yaml new file mode 100644 index 0000000000..33bb535744 --- /dev/null +++ b/argo/infra/k8ssandra-operator.yaml @@ -0,0 +1,54 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: k8ssandra-operator + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://helm.k8ssandra.io/stable + chart: k8ssandra-operator + targetRevision: 1.23.1 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/k8ssandra-operator.yaml + destination: + server: https://kubernetes.default.svc + namespace: k8ssandra-operator + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/keycloak-db.yaml b/argo/infra/keycloak-db.yaml new file mode 100644 index 0000000000..b6dd0bad87 --- /dev/null +++ b/argo/infra/keycloak-db.yaml @@ -0,0 +1,54 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: keycloak-db + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: 'registry-1.docker.io/bitnamicharts' + path: 'postgresql' + chart: postgresql + targetRevision: 16.6.6 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/keycloak-db.yaml + destination: + server: https://kubernetes.default.svc + namespace: keycloak + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/keycloak.yaml b/argo/infra/keycloak.yaml new file mode 100644 index 0000000000..101f27dd64 --- /dev/null +++ b/argo/infra/keycloak.yaml @@ -0,0 +1,52 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: keycloak + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://codecentric.github.io/helm-charts + chart: keycloakx + #targetRevision: 2.5.1 + targetRevision: 7.0.1 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/keycloak.yaml + destination: + server: https://kubernetes.default.svc + namespace: keycloak + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/kiali-instance/kiali-instance.yaml b/argo/infra/kiali-instance/kiali-instance.yaml new file mode 100644 index 0000000000..ad61fd8ae3 --- /dev/null +++ b/argo/infra/kiali-instance/kiali-instance.yaml @@ -0,0 +1,80 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: kiali.io/v1alpha1 +kind: Kiali +metadata: + name: kiali-instance + namespace: istio-system +spec: + # changing the app_label_name, as long as not all applications using "app" label + # istio_labels: + # app_label_name: "app.kubernetes.io/name" + api: + namespaces: + include: + - "keycloak" + - "kiali-operator" + - "onap" + - "postgres-operator" + - "kiali-operator" + - "k8ssandra-operator" + - "mariadb-operator" + - "mongodb-operator" + - "psmdb-operator" + - "nonrtric-rapp" + - "strimzi-system" + - "istio-ingress" + - "istio-system" + auth: + strategy: anonymous + istio_component_namespaces: + prometheus: cluster-observability + external_services: + grafana: + internal_url: "http://prometheus-grafana.prometheus" + url: "https://grafana." + auth: + username: "admin" + password: "prom-operator" + enabled: true + prometheus: + url: "http://prometheus-kube-prometheus-prometheus.prometheus:9090" + tracing: + enabled: false + external_url: https://jaeger. + internal_url: http://jaeger-query.istio-system:16685 + use_grpc: true + istio: + egress_gateway_namespace: istio-ingress + ingress_gateway_namespace: istio-ingress + deployment: + view_only_mode: false + security_context: + allowPrivilegeEscalation: false + capabilities: + drop: ["CAP_NET_RAW", "ALL"] + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + seccompProfile: + type: RuntimeDefault + server: + web_root: "/kiali" diff --git a/argo/infra/kiali-instance/kustomization.yaml b/argo/infra/kiali-instance/kustomization.yaml new file mode 100644 index 0000000000..13b6e21a01 --- /dev/null +++ b/argo/infra/kiali-instance/kustomization.yaml @@ -0,0 +1,23 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +kind: Kustomization +apiVersion: kustomize.config.k8s.io/v1beta1 + +resources: + - kiali-instance.yaml diff --git a/argo/infra/kiali-operator.yaml b/argo/infra/kiali-operator.yaml new file mode 100644 index 0000000000..5a71a1a14e --- /dev/null +++ b/argo/infra/kiali-operator.yaml @@ -0,0 +1,54 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: kiali-operator + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://kiali.org/helm-charts + chart: kiali-operator + targetRevision: 2.9.0 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/kiali-operator.yaml + destination: + server: https://kubernetes.default.svc + namespace: kiali-operator + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/kiali.yaml b/argo/infra/kiali.yaml new file mode 100644 index 0000000000..99f95ac88a --- /dev/null +++ b/argo/infra/kiali.yaml @@ -0,0 +1,40 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: kiali-instance + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: '' + targetRevision: + path: argo/infra/kiali-instance + destination: + server: https://kubernetes.default.svc + namespace: istio-system + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/kustomization.yaml b/argo/infra/kustomization.yaml new file mode 100644 index 0000000000..5b42a4c940 --- /dev/null +++ b/argo/infra/kustomization.yaml @@ -0,0 +1,42 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - cert-manager.yaml + - istio.yaml + - gateway-api.yaml + - chartmuseum.yaml + - kiali-operator.yaml + - kiali.yaml + - k8ssandra-operator.yaml + - keycloak-db.yaml + - keycloak.yaml + - jaeger.yaml + - mariadb-operator.yaml + - mariadb-operator-crds.yaml + - mongodb-operator.yaml + - postgres-operator.yaml + - nfs-server-provisioner.yaml + - strimzi.yaml + - prometheus.yaml + - infra-ingress.yaml + - django-defectdojo.yaml + - trivy-dojo-report-operator.yaml + - compile-onap.yaml diff --git a/argo/infra/mariadb-operator-crds.yaml b/argo/infra/mariadb-operator-crds.yaml new file mode 100644 index 0000000000..8bbf32b6af --- /dev/null +++ b/argo/infra/mariadb-operator-crds.yaml @@ -0,0 +1,52 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: mariadb-operator-crds + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://helm.mariadb.com/mariadb-operator + chart: mariadb-operator-crds + targetRevision: 0.38.1 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/mariadb-operator-crds.yaml + destination: + server: https://kubernetes.default.svc + namespace: mariadb-operator + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/mariadb-operator.yaml b/argo/infra/mariadb-operator.yaml new file mode 100644 index 0000000000..6790d1320e --- /dev/null +++ b/argo/infra/mariadb-operator.yaml @@ -0,0 +1,53 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: mariadb-operator + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://helm.mariadb.com/mariadb-operator + chart: mariadb-operator + targetRevision: 0.38.1 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/mariadb-operator.yaml + destination: + server: https://kubernetes.default.svc + namespace: mariadb-operator + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/mongodb-operator.yaml b/argo/infra/mongodb-operator.yaml new file mode 100644 index 0000000000..543ed51245 --- /dev/null +++ b/argo/infra/mongodb-operator.yaml @@ -0,0 +1,54 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: mongodb-operator + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://percona.github.io/percona-helm-charts + chart: psmdb-operator + targetRevision: 1.19.1 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/mongodb-operator.yaml + destination: + server: https://kubernetes.default.svc + namespace: mongodb-operator + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/nfs-server-provisioner.yaml b/argo/infra/nfs-server-provisioner.yaml new file mode 100644 index 0000000000..eae1b410af --- /dev/null +++ b/argo/infra/nfs-server-provisioner.yaml @@ -0,0 +1,49 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: nfs-server-provisioner + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://kvaps.github.io/charts + chart: nfs-server-provisioner + targetRevision: 1.8.0 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/nfs-server-provisioner.yaml + destination: + server: https://kubernetes.default.svc + namespace: nfs-server + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/postgres-operator.yaml b/argo/infra/postgres-operator.yaml new file mode 100644 index 0000000000..4415fdf462 --- /dev/null +++ b/argo/infra/postgres-operator.yaml @@ -0,0 +1,43 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: postgres-operator + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: 'https://github.com/CrunchyData/postgres-operator-examples' + targetRevision: main + path: kustomize/install/default + destination: + server: https://kubernetes.default.svc + namespace: postgres-operator + syncPolicy: + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/prometheus.yaml b/argo/infra/prometheus.yaml new file mode 100644 index 0000000000..d9721efccf --- /dev/null +++ b/argo/infra/prometheus.yaml @@ -0,0 +1,49 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: prometheus + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://prometheus-community.github.io/helm-charts + chart: kube-prometheus-stack + # targetRevision: 67.4.0 + targetRevision: 71.1.1 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/prometheus.yaml + destination: + server: https://kubernetes.default.svc + namespace: prometheus + syncPolicy: + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/strimzi.yaml b/argo/infra/strimzi.yaml new file mode 100644 index 0000000000..5c72bae868 --- /dev/null +++ b/argo/infra/strimzi.yaml @@ -0,0 +1,50 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: strimzi-kafka-operator + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://strimzi.io/charts/ + chart: strimzi-kafka-operator + targetRevision: 0.46.0 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/strimzi.yaml + destination: + server: https://kubernetes.default.svc + namespace: strimzi-system + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/trivy-dojo-report-operator.yaml b/argo/infra/trivy-dojo-report-operator.yaml new file mode 100644 index 0000000000..27a13ca615 --- /dev/null +++ b/argo/infra/trivy-dojo-report-operator.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: trivy-dojo-report-operator + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://telekom-mms.github.io/trivy-dojo-report-operator + chart: trivy-dojo-report-operator + targetRevision: 0.8.8 + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/infra/values/trivy-dojo-report-operator.yaml + destination: + server: https://kubernetes.default.svc + namespace: trividojo + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: disabled + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/infra/values/cert-manager.yaml b/argo/infra/values/cert-manager.yaml new file mode 100644 index 0000000000..2445061095 --- /dev/null +++ b/argo/infra/values/cert-manager.yaml @@ -0,0 +1,23 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +installCRDs: true + +prometheus: + servicemonitor: + enabled: false diff --git a/argo/infra/values/chartmuseum.yaml b/argo/infra/values/chartmuseum.yaml new file mode 100644 index 0000000000..7b7d6a8fb6 --- /dev/null +++ b/argo/infra/values/chartmuseum.yaml @@ -0,0 +1,42 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +replicaCount: 1 + +image: + repository: ghcr.io/helm/chartmuseum + tag: v0.16.2 + pullPolicy: IfNotPresent + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: false + +env: + open: + STORAGE: local + DISABLE_API: false + DEBUG: true + +persistence: + storageClass: + enabled: true + size: 8Gi diff --git a/argo/infra/values/compile-onap.yaml b/argo/infra/values/compile-onap.yaml new file mode 100644 index 0000000000..09c62079f6 --- /dev/null +++ b/argo/infra/values/compile-onap.yaml @@ -0,0 +1,23 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +repository_url: "http://chartmuseum.chartmuseum:8080" +onap_repo: "https://gerrit.onap.org/r/oom" +onap_repo_branch: "master" +gerrit_review: "" +gerrit_patchset: "" diff --git a/argo/infra/values/django-defectdojo.yaml b/argo/infra/values/django-defectdojo.yaml new file mode 100644 index 0000000000..301e4f90d6 --- /dev/null +++ b/argo/infra/values/django-defectdojo.yaml @@ -0,0 +1,69 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + defaultStorageClass: + +# Global settings +# create defectdojo specific secret +createSecret: true +# create redis secret in defectdojo chart, outside of redis chart +createRedisSecret: true +# create postgresql secret in defectdojo chart, outside of postgresql chart +createPostgresqlSecret: true +# create postgresql-ha secret in defectdojo chart, outside of postgresql-ha chart +createPostgresqlHaSecret: false +# create postgresql-ha-pgpool secret in defectdojo chart, outside of postgresql-ha chart +createPostgresqlHaPgpoolSecret: false +# Primary hostname of instance +host: defectdojo-django.defectdojo +alternativeHosts: + - defectdojo. +admin: + user: admin + password: gating +postgresql: + enabled: true + auth: + username: defectdojo + password: "defectdojo" + primary: + resources: + limits: + cpu: 500m + memory: 512Mi +redis: + auth: + password: "defectdojo" +django: + ingress: + enabled: false + uwsgi: + resources: + requests: + cpu: 300m + limits: + memory: 1Gi + appSettings: + maxFd: 102400 +extraEnv: + # Disable API token usage + #- name: DD_API_TOKENS_ENABLED + # value: "false" + #- name: DD_API_TOKEN_AUTH_ENDPOINT_ENABLED + # value: "false" \ No newline at end of file diff --git a/argo/infra/values/infra-ingress.yaml b/argo/infra/values/infra-ingress.yaml new file mode 100644 index 0000000000..ed43abd81e --- /dev/null +++ b/argo/infra/values/infra-ingress.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +post_addr: "" +dns_zone: "" diff --git a/argo/infra/values/istiod.yaml b/argo/infra/values/istiod.yaml new file mode 100644 index 0000000000..2cc8e36805 --- /dev/null +++ b/argo/infra/values/istiod.yaml @@ -0,0 +1,65 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + # configValidation enables the validation webhook for Istio configuration. + # TNAP: had to set it to False, as otherwise a setting is required: + # .Values.base.validationCABundle + configValidation: false + #hub: "/istio" + proxy: + holdApplicationUntilProxyStarts: true + +meshConfig: + enablePrometheusMerge: true + defaultConfig: + tracing: + zipkin: + address: jaeger-collector.istio-system:9411 + sampling: 100 + meshMTLS: + minProtocolVersion: TLSV1_3 + #tlsDefaults: + # Note: applicable only for non ISTIO_MUTUAL scenarios + # ecdhCurves: + # - P-256 + # - P-512 + rootNamespace: istio-config + extensionProviders: + - name: oauth2-proxy + envoyExtAuthzHttp: + service: oauth2-proxy.default.svc.cluster.local + port: 80 + timeout: 1.5s + includeHeadersInCheck: ["authorization", "cookie"] + headersToUpstreamOnAllow: ["x-forwarded-access-token", "authorization", "path", "x-auth-request-user", "x-auth-request-email", "x-auth-request-access-token"] + headersToDownstreamOnDeny: ["content-type", "set-cookie"] + +pilot: + env: + PILOT_ENABLE_ALPHA_GATEWAY_API: true + PILOT_HTTP10: true + ENABLE_NATIVE_SIDECARS: true + cni: + enabled: false + +istio_cni: + enabled: false + +base: + enableIstioConfigCRDs: false \ No newline at end of file diff --git a/argo/infra/values/jaeger.yaml b/argo/infra/values/jaeger.yaml new file mode 100644 index 0000000000..bdaca66a93 --- /dev/null +++ b/argo/infra/values/jaeger.yaml @@ -0,0 +1,35 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +fullnameOverride: jaeger + +collector: + service: + zipkin: + port: 9411 + otlp: + grpc: + name: otlp-grpc + port: 4317 + http: + name: otlp-http + port: 4318 + +query: + cmdlineParams: + query.max-clock-skew-adjustment: 300s diff --git a/argo/infra/values/k8ssandra-operator.yaml b/argo/infra/values/k8ssandra-operator.yaml new file mode 100644 index 0000000000..977ca8d569 --- /dev/null +++ b/argo/infra/values/k8ssandra-operator.yaml @@ -0,0 +1,42 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + clusterScoped: true + +image: + registry: + registryOverride: + +cass-operator: + image: + registry: + registryOverride: + admissionWebhooks: + enabled: true + +cleaner: + image: + registry: + +client: + image: + registry: + +# -- Allows managing CRD upgrades externally and fully disable the CRD upgrader job hook +disableCrdUpgraderJob: true diff --git a/argo/infra/values/keycloak-db.yaml b/argo/infra/values/keycloak-db.yaml new file mode 100644 index 0000000000..aeb3cc5310 --- /dev/null +++ b/argo/infra/values/keycloak-db.yaml @@ -0,0 +1,28 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + defaultStorageClass: + imageRegistry: + security: + allowInsecureImages: true + postgresql: + auth: + username: dbusername + password: dbpassword + database: keycloak \ No newline at end of file diff --git a/argo/infra/values/keycloak.yaml b/argo/infra/values/keycloak.yaml new file mode 100644 index 0000000000..46e58f245d --- /dev/null +++ b/argo/infra/values/keycloak.yaml @@ -0,0 +1,79 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +fullnameOverride: keycloak + +image: + tag: "26.0.6" + +command: + - "/opt/keycloak/bin/kc.sh" + - "--verbose" + - "start" + - "--http-port=8080" + - "--hostname-strict=false" + - "--spi-events-listener-jboss-logging-success-level=info" + - "--spi-events-listener-jboss-logging-error-level=warn" + +extraEnv: | + - name: KC_BOOTSTRAP_ADMIN_USERNAME + valueFrom: + secretKeyRef: + name: {{ include "keycloak.fullname" . }}-admin-creds + key: user + - name: KC_BOOTSTRAP_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "keycloak.fullname" . }}-admin-creds + key: password + - name: JAVA_OPTS_APPEND + value: >- + -XX:+UseContainerSupport + -XX:MaxRAMPercentage=50.0 + -Djava.awt.headless=true + -Djgroups.dns.query={{ include "keycloak.fullname" . }}-headless + - name: PROXY_ADDRESS_FORWARDING + value: "true" + +dbchecker: + enabled: true + image: + repository: /busybox + +database: + vendor: postgres + hostname: keycloak-db-postgresql + port: 5432 + username: dbusername + password: dbpassword + database: keycloak + +proxy: + enabled: true + mode: xforwarded + http: + enabled: true + +secrets: + admin-creds: + stringData: + user: admin + password: secret + +http: + relativePath: "/" diff --git a/argo/infra/values/kiali-operator.yaml b/argo/infra/values/kiali-operator.yaml new file mode 100644 index 0000000000..abb2f67cb5 --- /dev/null +++ b/argo/infra/values/kiali-operator.yaml @@ -0,0 +1,42 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + clusterScoped: true + +image: + registry: + registryOverride: + +cass-operator: + image: + registry: + registryOverride: + admissionWebhooks: + enabled: true + +cleaner: + image: + registry: + +client: + image: + registry: + +# -- Allows managing CRD upgrades externally and fully disable the CRD upgrader job hook +disableCrdUpgraderJob: true \ No newline at end of file diff --git a/argo/infra/values/mariadb-operator.yaml b/argo/infra/values/mariadb-operator.yaml new file mode 100644 index 0000000000..dd9a949510 --- /dev/null +++ b/argo/infra/values/mariadb-operator.yaml @@ -0,0 +1,29 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +ha: + enabled: true + +logLevel: "debug" + +metrics: + enabled: false + +webhook: + certificate: + certManager: true \ No newline at end of file diff --git a/argo/infra/values/mongodb-operator.yaml b/argo/infra/values/mongodb-operator.yaml new file mode 100644 index 0000000000..a38003081d --- /dev/null +++ b/argo/infra/values/mongodb-operator.yaml @@ -0,0 +1,46 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +watchAllNamespaces: true + +fullnameOverride: "percona-server-mongodb-operator" + +replicaCount: 1 + +resources: + requests: + cpu: 600m + memory: 700Mi + limits: + cpu: 1200m + memory: 1000Mi + +podSecurityContext: + runAsGroup: 65533 + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: RuntimeDefault + +securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - CAP_NET_RAW diff --git a/argo/infra/values/prometheus.yaml b/argo/infra/values/prometheus.yaml new file mode 100644 index 0000000000..d985e0c9b3 --- /dev/null +++ b/argo/infra/values/prometheus.yaml @@ -0,0 +1,57 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +prometheus: + agentMode: false + prometheusSpec: + ## If true, a nil or {} value for prometheus.prometheusSpec.ruleSelector will cause the + ## prometheus resource to be created with selectors based on values in the helm deployment, + ## which will also match the PrometheusRule resources created + ## + ruleSelectorNilUsesHelmValues: false + ## If true, a nil or {} value for prometheus.prometheusSpec.serviceMonitorSelector will cause the + ## prometheus resource to be created with selectors based on values in the helm deployment, + ## which will also match the servicemonitors created + ## + serviceMonitorSelectorNilUsesHelmValues: false + ## If true, a nil or {} value for prometheus.prometheusSpec.podMonitorSelector will cause the + ## prometheus resource to be created with selectors based on values in the helm deployment, + ## which will also match the podmonitors created + ## + podMonitorSelectorNilUsesHelmValues: false + additionalScrapeConfigs: + - job_name: 'istiod' + kubernetes_sd_configs: + - role: endpoints + namespaces: + names: + - istio-system + relabel_configs: + - source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] + action: keep + regex: istiod;http-monitoring + - job_name: 'envoy-stats' + metrics_path: /stats/prometheus + kubernetes_sd_configs: + - role: pod + scrape_interval: 5m + scrape_timeout: 1m + relabel_configs: + - source_labels: [__meta_kubernetes_pod_container_port_name] + action: keep + regex: '.*-envoy-prom' diff --git a/argo/infra/values/strimzi.yaml b/argo/infra/values/strimzi.yaml new file mode 100644 index 0000000000..9f20ee8721 --- /dev/null +++ b/argo/infra/values/strimzi.yaml @@ -0,0 +1,43 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +watchAnyNamespace: true + +podSecurityContext: + runAsGroup: 65533 + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: RuntimeDefault + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - CAP_NET_RAW + readOnlyRootFilesystem: true + runAsGroup: 65533 + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: RuntimeDefault + +config: + kafkaVersion: 4.0.0 + kafkaMetadataVersion: 4.0.0-IV3 \ No newline at end of file diff --git a/argo/infra/values/trivy-dojo-report-operator.yaml b/argo/infra/values/trivy-dojo-report-operator.yaml new file mode 100644 index 0000000000..55cb7362d2 --- /dev/null +++ b/argo/infra/values/trivy-dojo-report-operator.yaml @@ -0,0 +1,29 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +defectDojoApiCredentials: + apiKey: "2e5af2a04069492ea63cbd593efccfd4b2758b77" + url: "http://defectdojo-django.defectdojo" + #url: "https://defectdojo." + +operator: + trivyDojoReportOperator: + env: + defectDojoActive: "true" + defectDojoCloseOldFindings: "false" + defectDojoMinimumSeverity: Critical diff --git a/argo/onap-test/app-onap-test.yaml b/argo/onap-test/app-onap-test.yaml new file mode 100644 index 0000000000..77fe56169d --- /dev/null +++ b/argo/onap-test/app-onap-test.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-test + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io + labels: + name: onap-test +spec: + project: argo-management + source: + repoURL: '' + targetRevision: + path: ./argo/onap-test + destination: + server: https://kubernetes.default.svc + namespace: onap + syncPolicy: + automated: + prune: false + selfHeal: true + allowEmpty: false + syncOptions: + - Validate=true + - CreateNamespace=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + revisionHistoryLimit: 10 diff --git a/argo/onap-test/ingress-routes/helm/Chart.yaml b/argo/onap-test/ingress-routes/helm/Chart.yaml new file mode 100644 index 0000000000..3b96e8298c --- /dev/null +++ b/argo/onap-test/ingress-routes/helm/Chart.yaml @@ -0,0 +1,21 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +apiVersion: v2 +version: 0.0.1 +description: Chart to create gateway and Ingress Routes +name: ingress diff --git a/argo/onap-test/ingress-routes/helm/templates/ingress-kafka-ui.yaml b/argo/onap-test/ingress-routes/helm/templates/ingress-kafka-ui.yaml new file mode 100644 index 0000000000..6552dc3b4d --- /dev/null +++ b/argo/onap-test/ingress-routes/helm/templates/ingress-kafka-ui.yaml @@ -0,0 +1,58 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: kafka-ui-route + namespace: onap +spec: + parentRefs: + - name: common-gateway + sectionName: https + namespace: istio-ingress + hostnames: + - kafka-ui{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: kafka-ui + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: kafka-ui-redirect-route + namespace: onap +spec: + parentRefs: + - name: common-gateway + sectionName: http + namespace: istio-ingress + hostnames: + - kafka-ui{{ .Values.post_addr }}.{{ .Values.dns_zone }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + port: 443 diff --git a/argo/onap-test/ingress-routes/helm/values.yaml b/argo/onap-test/ingress-routes/helm/values.yaml new file mode 100644 index 0000000000..d282f5e9a3 --- /dev/null +++ b/argo/onap-test/ingress-routes/helm/values.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +post_addr: "" +dns_zone: "" diff --git a/argo/onap-test/kafka-ui.yaml b/argo/onap-test/kafka-ui.yaml new file mode 100644 index 0000000000..db8cc2b508 --- /dev/null +++ b/argo/onap-test/kafka-ui.yaml @@ -0,0 +1,48 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: kafka-ui + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://kafbat.github.io/helm-charts + chart: kafka-ui + targetRevision: 1.5.0 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap-test/values/kafka-ui.yaml + destination: + server: https://kubernetes.default.svc + namespace: onap + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap-test/kustomization.yaml b/argo/onap-test/kustomization.yaml new file mode 100644 index 0000000000..7518ca39e8 --- /dev/null +++ b/argo/onap-test/kustomization.yaml @@ -0,0 +1,29 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +generatorOptions: + disableNameSuffixHash: true + +resources: + - testkube.yaml + - kafka-ui.yaml + - trivy-operator.yaml + - onap-test-ingress.yaml diff --git a/argo/onap-test/onap-test-ingress.yaml b/argo/onap-test/onap-test-ingress.yaml new file mode 100644 index 0000000000..fce16708c0 --- /dev/null +++ b/argo/onap-test/onap-test-ingress.yaml @@ -0,0 +1,60 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-test-ingress + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io + labels: + name: onap-test-ingress +spec: + project: argo-management + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: + targetRevision: + path: ./argo/onap-test/ingress-routes/helm + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap-test/values/onap-test-ingress.yaml + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + automated: + prune: false + selfHeal: true + allowEmpty: false + syncOptions: + - Validate=true + - CreateNamespace=true + #- PrunePropagationPolicy=foreground + #- PruneLast=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + revisionHistoryLimit: 10 diff --git a/argo/onap-test/testkube.yaml b/argo/onap-test/testkube.yaml new file mode 100644 index 0000000000..8243884ed1 --- /dev/null +++ b/argo/onap-test/testkube.yaml @@ -0,0 +1,54 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: testkube + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io + labels: + name: testkube +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: + targetRevision: + path: ./argo/onap-test/testkube/helm + helm: + # Values file as block file. This takes precedence over values + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap-test/values/testkube.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap-test/testkube/helm/Chart.yaml b/argo/onap-test/testkube/helm/Chart.yaml new file mode 100644 index 0000000000..44c541c02a --- /dev/null +++ b/argo/onap-test/testkube/helm/Chart.yaml @@ -0,0 +1,26 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +apiVersion: v2 +name: onapTestkube +description: A Helm chart with tests for onap +type: application +version: 0.0.1 +dependencies: + - name: testkube + version: 1.16.63 + repository: 'https://kubeshop.github.io/helm-charts' diff --git a/argo/onap-test/testkube/helm/templates/cluster-role-binding.yaml b/argo/onap-test/testkube/helm/templates/cluster-role-binding.yaml new file mode 100644 index 0000000000..0bef9b5fe0 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/cluster-role-binding.yaml @@ -0,0 +1,31 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: rbac.authorization.k8s.io/v1 +# This cluster role binding allows anyone in the "manager" group to read secrets in any namespace. +kind: ClusterRoleBinding +metadata: + name: {{ .Release.Name }}-tests-cluster-role-binding +subjects: +- kind: ServiceAccount + name: {{ .Release.Name }}-tests-service-account + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ .Release.Name }}-tests-cluster-role + apiGroup: rbac.authorization.k8s.io diff --git a/argo/onap-test/testkube/helm/templates/cluster-role.yaml b/argo/onap-test/testkube/helm/templates/cluster-role.yaml new file mode 100644 index 0000000000..a8f5bceab9 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/cluster-role.yaml @@ -0,0 +1,129 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Name }}-tests-cluster-role +rules: +- apiGroups: + - '' + - apps + - batch + - extensions + - networking.k8s.io + - gateway.networking.k8s.io + - tf.galleybytes.com + - aquasecurity.github.io + - onap.com + resources: + - pods + - deployments + - deployments/status + - jobs + - jobs/status + - statefulsets + - replicasets + - replicasets/status + - daemonsets + - secrets + - services + - events + - configmaps + - ingresses + - persistentvolumeclaims + - nodes + - terraforms + - httproutes + - namespaces + - serviceinstances + - vnfs + - vulnerabilityreports + verbs: + - get + - watch + - list +- apiGroups: + - '' + - apps + resources: + - statefulsets + - configmaps + verbs: + - patch +- apiGroups: + - '' + - apps + - tf.galleybytes.com + - onap.com + resources: + - deployments + - daemonsets + - statefulsets + - secrets + - services + - pods + - terraforms + - namespaces + - configmaps + - serviceinstances + - vnfs + verbs: + - create +- apiGroups: + - '' + - apps + - tf.galleybytes.com + - onap.com + resources: + - pods + - persistentvolumeclaims + - secrets + - deployments + - daemonsets + - statefulsets + - services + - terraforms + - configmaps + - serviceinstances + - vnfs + verbs: + - delete +- apiGroups: + - '' + - apps + resources: + - pods/exec + verbs: + - create +- apiGroups: + - cert-manager.io + resources: + - certificates + verbs: + - create + - delete +{{- if .Values.tests.tests.basicKafka.enabled }} +- apiGroups: + - kafka.strimzi.io + resources: + - kafkatopics + verbs: + - create + - delete +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/control-panel-basic-executor.yaml b/argo/onap-test/testkube/helm/templates/control-panel-basic-executor.yaml new file mode 100644 index 0000000000..22a3a25aaf --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/control-panel-basic-executor.yaml @@ -0,0 +1,37 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: executor.testkube.io/v1 +kind: Executor +metadata: + name: {{ .Values.tests.smokeTests.executor.controlPanelSdk.name }} +spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 4 }} + {{- end }} + image: /onap/control-panel-ui-smoke-tests/controlpanel-smoke-tests:{{ .Values.tests.smokeTests.executor.controlPanelSdk.imageVersion }} + command: + - "/bin/bash" + - "-c" + - "./gradlew --offline -p controlpanel-smoke-tests test \"-Dcucumber.filter.tags=${CUCUMBER_FILTER_TAGS}\" " + executor_type: container + types: + - {{ .Values.tests.smokeTests.executor.controlPanelSdk.type }} + features: + - artifacts diff --git a/argo/onap-test/testkube/helm/templates/control-panel-smoke-test.yaml b/argo/onap-test/testkube/helm/templates/control-panel-smoke-test.yaml new file mode 100644 index 0000000000..42310c1c50 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/control-panel-smoke-test.yaml @@ -0,0 +1,43 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +{{- if .Values.tests.tests.controlPanelSmokeTest.enabled }} +apiVersion: tests.testkube.io/v3 +kind: Test +metadata: + name: {{ .Values.tests.tests.controlPanelSmokeTest.testName }} +spec: + type: {{ .Values.tests.smokeTests.executor.controlPanelSdk.type }} + executionRequest: + variables: + CUCUMBER_FILTER_TAGS: + name: CUCUMBER_FILTER_TAGS + type: basic + CONTROLPANEL_ENV: + name: CONTROLPANEL_ENV + value: {{ .Values.tests.testEnvName }} + type: basic + activeDeadlineSeconds: {{ .Values.tests.smokeTests.execution.activeDeadlineSeconds }} + artifactRequest: + storageClassName: {{ .Values.tests.smokeTests.artifacts.storageClassName }} + volumeMountPath: /app/test-artifacts +{{- include "job.template" . | indent 4 }} +{{- if .Values.global.serviceMesh.enabled }} +{{- include "scraper.template" . | indent 4 }} +{{- end }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/cypress-tests/cypress-test.tpl b/argo/onap-test/testkube/helm/templates/cypress-tests/cypress-test.tpl new file mode 100644 index 0000000000..ce96733a39 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/cypress-tests/cypress-test.tpl @@ -0,0 +1,69 @@ +{{/* https://docs.testkube.io/articles/crds/tests.testkube.io-v3 */}} +{{/* +Common test template for cypress tests + +@param .dot (Optional, default .) The root scope +@param .repo A map representing the repository configuration + The map must contain at least the following fields: + .repo.uri: the uri of the git repo that + contains the cypress project + .repo.branch the branch of the git repo that + contains the cypress project +@param .test A map representing a single test + The map must contain at least the following fields: + .test.name: The name of the test + The map may contain the following optional fields: + .test.env: environment variables for the container + +Example include: + {{ include "cypress.test" (dict "repo" .Values.tests.cypress "test" .Values.tests.cypress.tests.aai) }} +*/}} +{{- define "cypress.test" }} +apiVersion: tests.testkube.io/v3 +kind: Test +metadata: + name: {{ kebabcase .test.testName }} +spec: + type: cypress/project + content: + type: git + repository: + type: git + uri: {{ .repo.uri }} + branch: {{ .test.branch | default .repo.branch }} + tokenSecret: + key: git-token + name: testkube-git-creds + usernameSecret: + key: git-username + name: testkube-git-creds + executionRequest: + activeDeadlineSeconds: 1800 + jobTemplate: | + apiVersion: batch/v1 + kind: Job + metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + argocd.argoproj.io/sync-options: Prune=false + spec: + template: + metadata: + labels: + sidecar.istio.io/inject: 'false' + spec: + containers: + - name: {{ kebabcase .test.testName }} + image: {{ .repo.image }} + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 300m + memory: 300Mi + {{- if .test.env }} + envs: + {{- range $key, $value := .test.env }} + {{ $key }}: {{ $value | quote }} + {{ end -}} + {{ end -}} +{{ end -}} diff --git a/argo/onap-test/testkube/helm/templates/cypress-tests/demo-test.yaml b/argo/onap-test/testkube/helm/templates/cypress-tests/demo-test.yaml new file mode 100644 index 0000000000..9fd7148106 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/cypress-tests/demo-test.yaml @@ -0,0 +1,44 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: tests.testkube.io/v3 +kind: Test +metadata: + name: cypress-test-3 +spec: + type: cypress/project + content: + type: git-dir + repository: + type: git + uri: https://github.com/kubeshop/testkube.git + branch: main + path: test/cypress/executor-tests/cypress-14 + executionRequest: + variables: + CYPRESS_CUSTOM_ENV: + name: CYPRESS_CUSTOM_ENV + value: "CYPRESS_CUSTOM_ENV_value" + type: basic + DEBUG: + name: DEBUG + value: "cypress:*" + type: basic + args: + - "--env" + - "NON_CYPRESS_ENV=NON_CYPRESS_ENV_value" diff --git a/argo/onap-test/testkube/helm/templates/cypress-tests/portalng-ui-test.yaml b/argo/onap-test/testkube/helm/templates/cypress-tests/portalng-ui-test.yaml new file mode 100644 index 0000000000..5c7d29cb1b --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/cypress-tests/portalng-ui-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.cypress.tests.portalng.someTest.enabled }} +{{ include "cypress.test" (dict "repo" .Values.tests.cypress "test" .Values.tests.cypress.tests.portalng.someTest) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/gradle-tests/aai-crud-test.yaml b/argo/onap-test/testkube/helm/templates/gradle-tests/aai-crud-test.yaml new file mode 100644 index 0000000000..316f772b3e --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/gradle-tests/aai-crud-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.gradle.tests.aai.aaiCrudTest.enabled }} +{{ include "gradle.test" (dict "repo" .Values.tests.gradle "test" .Values.tests.gradle.tests.aai.aaiCrudTest) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/gradle-tests/aai-kafka-test.yaml b/argo/onap-test/testkube/helm/templates/gradle-tests/aai-kafka-test.yaml new file mode 100644 index 0000000000..d96cf54375 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/gradle-tests/aai-kafka-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.gradle.tests.aai.kafkaTest.enabled }} +{{ include "gradle.test" (dict "repo" .Values.tests.gradle "test" .Values.tests.gradle.tests.aai.kafkaTest) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/gradle-tests/aai-traversal-test.yaml b/argo/onap-test/testkube/helm/templates/gradle-tests/aai-traversal-test.yaml new file mode 100644 index 0000000000..d5cf4ac7e4 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/gradle-tests/aai-traversal-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.gradle.tests.aai.aaiTraversalTest.enabled }} +{{ include "gradle.test" (dict "repo" .Values.tests.gradle "test" .Values.tests.gradle.tests.aai.aaiTraversalTest) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/gradle-tests/gradle-test.tpl b/argo/onap-test/testkube/helm/templates/gradle-tests/gradle-test.tpl new file mode 100644 index 0000000000..7bfd50f8bb --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/gradle-tests/gradle-test.tpl @@ -0,0 +1,75 @@ +{{/* https://docs.testkube.io/articles/crds/tests.testkube.io-v3 */}} +{{/* +Common test template for gradle tests + +@param .dot (Optional, default .) The root scope +@param .repo A map representing the repository configuration + The map must contain at least the following fields: + .repo.uri: the uri of the git repo that + contains the gradle project + .repo.branch the branch of the git repo that + contains the gradle project +@param .test A map representing a single test + The map must contain at least the following fields: + .test.name: The name of the test + The map may contain the following optional fields: + .test.env: environment variables for the container + +Example include: + {{ include "gradle.test" (dict "repo" .Values.tests.gradle "test" .Values.tests.gradle.tests.aai) }} +*/}} +{{- define "gradle.test" }} +apiVersion: tests.testkube.io/v3 +kind: Test +metadata: + name: {{ kebabcase .test.testName }} +spec: + type: gradle/test + content: + type: git + repository: + type: git + uri: {{ .repo.uri }} + branch: {{ .test.branch | default .repo.branch }} + tokenSecret: + key: git-token + name: testkube-git-creds + usernameSecret: + key: git-username + name: testkube-git-creds + executionRequest: + args: + - "--tests" + - {{ .test.testName | quote }} + {{- if .test.debugLogEnabled }} + - "--info" + {{- end }} + activeDeadlineSeconds: 1800 + jobTemplate: | + apiVersion: batch/v1 + kind: Job + metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + argocd.argoproj.io/sync-options: Prune=false + spec: + template: + metadata: + labels: + sidecar.istio.io/inject: 'false' + spec: + containers: + - name: {{ kebabcase .test.testName }} + image: {{ .repo.image }} + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 300m + memory: 300Mi + {{- if .test.env }} + envs: + {{- range $key, $value := .test.env }} + {{ $key }}: {{ $value | quote }} + {{ end -}} + {{ end -}} +{{ end -}} diff --git a/argo/onap-test/testkube/helm/templates/ingress.yaml b/argo/onap-test/testkube/helm/templates/ingress.yaml new file mode 100644 index 0000000000..0f40a3e236 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/ingress.yaml @@ -0,0 +1,91 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: testkube-route + namespace: onap +spec: + parentRefs: + - name: common-gateway + sectionName: https + namespace: istio-ingress + hostnames: + - testkube{{ .Values.global.ingress.post_addr }}.{{ .Values.global.ingress.dns_zone }} + rules: + - backendRefs: + - name: testkube-dashboard + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: testkube-redirect-route + namespace: onap +spec: + parentRefs: + - name: common-gateway + sectionName: http + namespace: istio-ingress + hostnames: + - testkube{{ .Values.global.ingress.post_addr }}.{{ .Values.global.ingress.dns_zone }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + port: 443 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: testkube-api-route + namespace: onap +spec: + parentRefs: + - name: common-gateway + sectionName: https + namespace: istio-ingress + hostnames: + - testkube-api{{ .Values.global.ingress.post_addr }}.{{ .Values.global.ingress.dns_zone }} + rules: + - backendRefs: + - name: testkube-api-server + port: 8088 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: testkube-api-redirect-route + namespace: onap +spec: + parentRefs: + - name: common-gateway + sectionName: http + namespace: istio-ingress + hostnames: + - testkube-api{{ .Values.global.ingress.post_addr }}.{{ .Values.global.ingress.dns_zone }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + port: 443 diff --git a/argo/onap-test/testkube/helm/templates/job-template.tpl b/argo/onap-test/testkube/helm/templates/job-template.tpl new file mode 100644 index 0000000000..9693c551ff --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/job-template.tpl @@ -0,0 +1,18 @@ +{{- define "job.template" }} +{{/* Define job.template */}} +jobTemplate: | + apiVersion: batch/v1 + kind: Job + metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + argocd.argoproj.io/sync-options: Prune=false + spec: + template: + spec: + serviceAccountName: {{ .Release.Name }}-tests-service-account + containers: + - name: {{ printf "\"{{ .Name }}\"" }} + image: {{ printf "{{ .Image }}" }} + imagePullPolicy: Always +{{ end -}} diff --git a/argo/onap-test/testkube/helm/templates/onap-smoke-tests-testsuite.yaml b/argo/onap-test/testkube/helm/templates/onap-smoke-tests-testsuite.yaml new file mode 100644 index 0000000000..0ab83b5069 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/onap-smoke-tests-testsuite.yaml @@ -0,0 +1,164 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: tests.testkube.io/v3 +kind: TestSuite +metadata: + name: {{ .Values.tests.smokeTests.testsuite.name }} +spec: + schedule: "{{ .Values.tests.smokeTests.testsuite.cron }}" + steps: + {{- /* Gradle tests */}} + - execute: + {{- range $usecase := .Values.tests.gradle.tests }} + {{- range $test := $usecase }} + {{- if $test.enabled }} + - test: {{ kebabcase $test.testName }} + {{- end }} + {{- end }} + {{- end }} + stopOnFailure: false + {{- /* Smoke tests */}} + {{- /* Basic tests group */}} + - stopOnFailure: false + execute: + {{- $test := .Values.tests.tests.basicCps }} + {{- if $test.enabled }} + - test: {{ $test.testName }} + {{- end }} + {{- $test := .Values.tests.tests.basicOnboard }} + {{- if $test.enabled }} + - test: {{ $test.testName }} + {{- end }} + {{- $test := .Values.tests.tests.basicNetwork }} + {{- if $test.enabled }} + - test: {{ $test.testName }} + {{- end }} + {{- $test := .Values.tests.tests.basicCds }} + {{- if $test.enabled }} + - test: {{ $test.testName }} + {{- end }} + {{- $test := .Values.tests.tests.basicSdnc }} + {{- if $test.enabled }} + - test: {{ $test.testName }} + {{- end }} + {{- $test := .Values.tests.tests.basicCnfMacro }} + {{- if $test.enabled }} + - test: {{ $test.testName }} + {{- end }} + {{- $test := .Values.tests.tests.controlPanelSmokeTest }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.aaiInitialDataSetup }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.serviceWithoutResource }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.addDeletePnfInRunningService }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.pnfWithVesEvent }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.pnfWithoutVesEvent }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.pnfMacro }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.basicPrh }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.checkTimeSync }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.basicStatus }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.basicKafka }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.addDeleteCnfMacro }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.policyFramework }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- $test := .Values.tests.tests.vesPublish }} + {{- if $test.enabled }} + - execute: + - test: {{ $test.testName }} + stopOnFailure: false + {{- end }} + {{- if .Values.global.serviceMesh.enabled }} + executionRequest: + cronJobTemplate: | + apiVersion: batch/v1 + kind: CronJob + metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + argocd.argoproj.io/sync-options: Prune=false + spec: + jobTemplate: + spec: + activeDeadlineSeconds: {{ .Values.tests.smokeTests.testsuite.testsuiteJobActiveDeadlineSeconds }} + template: + spec: + serviceAccountName: {{ .Release.Name }}-tests-service-account + {{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-smoke-test.tpl b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-smoke-test.tpl new file mode 100644 index 0000000000..8876d3fc9a --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-smoke-test.tpl @@ -0,0 +1,81 @@ +{{- define "sidecarKiller" }} +{{/* +{{ include "sidecarKiller" (dict "containerName" "containerNameToCheck" "Values" .Values) }} +*/}} +- name: sidecar-killer + image: {{ .Values.serviceMesh.sidecarKiller.image }} + command: ["/bin/sh", "-c"] + args: ["echo \"waiting 10s for istio side cars to be up\"; sleep 10s; /app/ready.py --service-mesh-check {{ .containerName }} -t 45;"] + env: + - name: NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace +{{ end -}} + +{{- define "smoke.test" }} +{{/* Define smoke test template */}} +{{- $dot := default . .dot -}} +{{- $configurationName := default .onapTestName .configurationName }} +{{- $executor := default $dot.Values.tests.smokeTests.executor.pythonsdk.type .executor }} +{{- $testEnv := default $dot.Values.tests.testEnvName .testEnvName }} +{{- $schedule := default "" .schedule }} +{{/* - if hasKey $dot.Values.tests.configuration $executor */}} +{{- $executorRepoConfig := get $dot.Values.tests.configuration $executor }} +{{- $uri := default "" $executorRepoConfig.uri }} +{{- $branch := default "master" $executorRepoConfig.branch }} +{{- $path := default "/" $executorRepoConfig.path }} +{{/* - else */}} +{{/* - fail "Executor has to have git configuration set in .Values.tests.configuration" -*/}} +{{/*- end */}} +apiVersion: tests.testkube.io/v3 +kind: Test +metadata: + name: {{ .testName }} +spec: + type: {{ $executor }} + executionRequest: + args: + - $(TESTNAME) + envs: + NAMESPACE: "{{ $dot.Values.namespace }}" + TESTNAME: {{ .onapTestName }} + PYTHONPATH: $PYTHONPATH:/data/repo{{ $path }}/basic_configuration_settings + ONAP_PYTHON_SDK_SETTINGS: "{{ $configurationName }}.{{ $configurationName }}_configuration" + TEST_ENV_NAME: "{{ $testEnv }}" + {{- if $dot.Values.tests.slackNotifications.enabled }} + SLACK_TOKEN: "{{ $dot.Values.tests.slackNotifications.slackConfig.token }}" + SLACK_URL: {{ $dot.Values.tests.slackNotifications.slackConfig.baseUrl }} + SLACK_CHANNEL: "{{ $dot.Values.tests.slackNotifications.slackConfig.channel }}" + {{- end }} + {{- if $dot.Values.global.serviceMesh.enabled }} + {{- range $key, $val := $dot.Values.serviceMesh.envVariable }} + {{ $key }}: {{ $val | quote }} + {{- end }} + {{- end }} + artifactRequest: + storageClassName: {{ $dot.Values.tests.smokeTests.artifacts.storageClassName }} + volumeMountPath: /tmp + activeDeadlineSeconds: {{ $dot.Values.tests.smokeTests.execution.activeDeadlineSeconds }} + {{- include "job.template" $dot | indent 4 }} + {{- if $dot.Values.global.serviceMesh.enabled }} + {{- include "scraper.template" $dot | indent 4 }} + {{- end }} + content: + type: git-file + repository: + type: git + uri: {{ $uri }} + branch: {{ $branch }} + path: {{ $path }} + tokenSecret: + key: git-token + name: {{ $executorRepoConfig.secretName | default "tnap-testkube-git-creds" }} + usernameSecret: + key: git-username + name: {{ $executorRepoConfig.secretName | default "tnap-testkube-git-creds" }} + {{- if $schedule }} + schedule: "{{ $schedule }}" + {{- end }} +{{ end -}} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-aai-initial-data-setup.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-aai-initial-data-setup.yaml new file mode 100644 index 0000000000..d6ff397955 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-aai-initial-data-setup.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.aaiInitialDataSetup.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.aaiInitialDataSetup.testName "onapTestName" "aai_initial_data_setup" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-add-delete-cnf-macro.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-add-delete-cnf-macro.yaml new file mode 100644 index 0000000000..52c541910d --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-add-delete-cnf-macro.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.addDeleteCnfMacro.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.addDeleteCnfMacro.testName "onapTestName" "add_delete_cnf_macro" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-add-delete-pnf-in-running-service.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-add-delete-pnf-in-running-service.yaml new file mode 100644 index 0000000000..82f0035be9 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-add-delete-pnf-in-running-service.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.addDeletePnfInRunningService.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.addDeletePnfInRunningService.testName "onapTestName" "add_pnf_in_running_service" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cds-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cds-test.yaml new file mode 100644 index 0000000000..0027e65299 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cds-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.basicCds.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.basicCds.testName "onapTestName" "basic_cds" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cnf-macro.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cnf-macro.yaml new file mode 100644 index 0000000000..9c935857a4 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cnf-macro.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.basicCnfMacro.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.basicCnfMacro.testName "onapTestName" "basic_cnf_macro" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cps-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cps-test.yaml new file mode 100644 index 0000000000..58e665af01 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-cps-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.basicCps.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.basicCps.testName "onapTestName" "basic_cps" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-executor.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-executor.yaml new file mode 100644 index 0000000000..62a89cd733 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-executor.yaml @@ -0,0 +1,37 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: executor.testkube.io/v1 +kind: Executor +metadata: + name: {{ .Values.tests.smokeTests.executor.pythonsdk.name }} +spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 4 }} + {{- end }} + image: {{ .Values.tests.smokeTests.executor.pythonsdk.image }} + command: + - /bin/sh + - -c + - run_tests -t ${TESTNAME} + executor_type: container + types: + - {{ .Values.tests.smokeTests.executor.pythonsdk.type }} + features: + - artifacts diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-kafka-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-kafka-test.yaml new file mode 100644 index 0000000000..3d3439177e --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-kafka-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.basicKafka.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.basicKafka.testName "onapTestName" "basic_kafka" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-network-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-network-test.yaml new file mode 100644 index 0000000000..8c8b48495a --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-network-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.basicNetwork.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.basicNetwork.testName "onapTestName" "basic_network" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-onboard-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-onboard-test.yaml new file mode 100644 index 0000000000..4c201c81c9 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-onboard-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.basicOnboard.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.basicOnboard.testName "onapTestName" "basic_onboard" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-prh-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-prh-test.yaml new file mode 100644 index 0000000000..8d506c9ea5 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-prh-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.basicPrh.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.basicPrh.testName "onapTestName" "basic_prh" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-sdnc-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-sdnc-test.yaml new file mode 100644 index 0000000000..1d74ce6e12 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-sdnc-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.basicSdnc.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.basicSdnc.testName "onapTestName" "basic_sdnc" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-status-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-status-test.yaml new file mode 100644 index 0000000000..7d9bc5e999 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-basic-status-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.basicStatus.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.basicStatus.testName "onapTestName" "status" "configurationName" "basic_status" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-check-time-sync.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-check-time-sync.yaml new file mode 100644 index 0000000000..08a38f909b --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-check-time-sync.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.checkTimeSync.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.checkTimeSync.testName "onapTestName" "check_time_sync" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-full-status-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-full-status-test.yaml new file mode 100644 index 0000000000..a4fa8c161d --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-full-status-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.fullStatus.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.fullStatus.testName "onapTestName" "status" "configurationName" "full_status" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-macro-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-macro-test.yaml new file mode 100644 index 0000000000..d5b8386da5 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-macro-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.pnfMacro.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.pnfMacro.testName "onapTestName" "pnf_macro" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-with-ves-event.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-with-ves-event.yaml new file mode 100644 index 0000000000..5775cbea0b --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-with-ves-event.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.pnfWithVesEvent.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.pnfWithVesEvent.testName "onapTestName" "pnf_with_ves_event" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-without-ves-event.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-without-ves-event.yaml new file mode 100644 index 0000000000..3cc89eb956 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-pnf-without-ves-event.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.pnfWithoutVesEvent.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.pnfWithoutVesEvent.testName "onapTestName" "instantiate_pnf_without_registration_event" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-policy-framework.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-policy-framework.yaml new file mode 100644 index 0000000000..76d35831c3 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-policy-framework.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.policyFramework.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.policyFramework.testName "onapTestName" "basic_policy" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-service-without-res.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-service-without-res.yaml new file mode 100644 index 0000000000..02e630235e --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-service-without-res.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.serviceWithoutResource.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.serviceWithoutResource.testName "onapTestName" "instantiate_service_without_resource" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-ves-test.yaml b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-ves-test.yaml new file mode 100644 index 0000000000..6c7f9c27e9 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/pythonsdk-tests/pythonsdk-tests-ves-test.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +{{- if .Values.tests.tests.vesPublish.enabled }} +{{ include "smoke.test" (dict "testName" .Values.tests.tests.vesPublish.testName "onapTestName" "ves_publish" "dot" .) }} +{{- end }} diff --git a/argo/onap-test/testkube/helm/templates/robot-tests/healthcheck.yaml b/argo/onap-test/testkube/helm/templates/robot-tests/healthcheck.yaml new file mode 100644 index 0000000000..328dbd0061 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/robot-tests/healthcheck.yaml @@ -0,0 +1,70 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +#--- +# apiVersion: testworkflows.testkube.io/v1 +# kind: TestWorkflow +# metadata: +# name: integration-onap +# namespace: onap +# spec: +# content: +# container: +# image: nexus3.onap.org:10001/onap/xtesting-healthcheck:latest +# imagePullPolicy: Always +# env: +# - name: INSTALLER_TYPE +# value: "{{ .Values.config.deployment_name }}" +# - name: DEPLOY_SCENARIO +# value: "{{ .Values.config.deploy_scenario }}" +# - name: NODE_NAME +# value: "{{ .Values.config.node_name }}" +# - name: TEST_DB_URL +# value: http://testresults.opnfv.org/onap/api/v1/results +# - name: BUILD_TAG +# value: "{{ .Values.config.build_tag }}" +# - name: TAG +# value: "{{ .Values.config.run_type }}" +# volumeMounts: +# - mountPath: /etc/localtime +# name: localtime +# - mountPath: /share/config +# name: robot-eteshare +# - mountPath: /var/lib/xtesting/results/ +# name: robot-save-results + +# volumes: +# - name: localtime +# hostPath: +# path: /etc/localtime +# - name: robot-eteshare +# configMap: +# name: onap-robot-eteshare-configmap +# - name: robot-save-results +# hostPath: +# path: "{{ .Values.config.res_local_path }}" + +# steps: +# - name: run-robot-tests +# shell: | +# robot --outputdir /var/lib/xtesting/results/ /path/to/your/tests/ + +# artifacts: +# paths: +# - /var/lib/xtesting/results/* +# storageClassName: standard +# volumeSize: 1Gi diff --git a/argo/onap-test/testkube/helm/templates/scraper-template.tpl b/argo/onap-test/testkube/helm/templates/scraper-template.tpl new file mode 100644 index 0000000000..bef7b2d6e4 --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/scraper-template.tpl @@ -0,0 +1,25 @@ +{{- define "scraper.template" }} +{{/* Define scraper.template */}} +scraperTemplate: | + apiVersion: batch/v1 + kind: Job + metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + argocd.argoproj.io/sync-options: Prune=false + spec: + template: + spec: + serviceAccountName: {{ .Release.Name }}-tests-service-account + containers: + - name: {{ printf "\"{{ .Name }}-scraper\"" }} + {{ printf "{{- if .Registry }}" }} + image: {{ printf "{{ .Registry }}/{{ .ScraperImage }}" }} + {{ printf "{{- else }}" }} + image: {{ printf "{{ .ScraperImage }}" }} + {{ printf "{{- end }}" }} + imagePullPolicy: Always + command: + - "/bin/runner" + - {{ printf "'{{ .Jsn }}'" }} +{{ end -}} diff --git a/argo/onap-test/testkube/helm/templates/service-account.yaml b/argo/onap-test/testkube/helm/templates/service-account.yaml new file mode 100644 index 0000000000..36a2869a6c --- /dev/null +++ b/argo/onap-test/testkube/helm/templates/service-account.yaml @@ -0,0 +1,22 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Release.Name }}-tests-service-account diff --git a/argo/onap-test/testkube/helm/values.yaml b/argo/onap-test/testkube/helm/values.yaml new file mode 100644 index 0000000000..af8c2573bf --- /dev/null +++ b/argo/onap-test/testkube/helm/values.yaml @@ -0,0 +1,427 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +# Default values for tnapTestkube. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +global: + ingress: + enabled: false + post_addr: &postrAddr "" + dns_zone: &dnsZone "" + serviceMesh: + enabled: false + renderPullSecrets: True + defaultStorageClass: + storageClass: +ingress: + host: to-be-changed + +namespace: onap + +serviceMesh: + envVariable: + SERVICE_MESH_ENABLED: True + sidecarKiller: + image: /onap/oom/readiness:4.2.0 + +imagePullSecrets: + - name: onap-docker-registry-key + +tests: + testEnvName: "" + configuration: + pythonsdk-tests/smoke-test: + uri: + path: /argo/onap-test/testkube/pythonsdk-tests + branch: main + secretName: testkube-git-creds + gradle: + uri: https://git.onap.org/integration/java-tests + branch: main + image: /kubeshop/testkube-gradle-executor:1.16.39 + tests: + aai: + aaiTraversalTest: + enabled: true + testName: TraversalTest + env: + AAI_BASEURL: http://aai.onap/aai/v30 + aaiCrudTest: + enabled: true + testName: AAICrudTest + env: + AAI_BASEURL: http://aai.onap/aai/v30 + kafkaTest: + enabled: false + testName: KafkaTest + branch: kafka + env: + AAI_BASEURL: http://aai.onap/aai/v30 + cypress: + uri: https://git.onap.org/integration/cypress-tests + branch: main + image: /kubeshop/testkube-cypress-executor:1.16.39 + tests: + portalng: + someTest: + enabled: true + testName: foo + env: + CYPRESS_KEYCLOAK_URL: https://keycloak-ui. + CYPRESS_PORTAL_NG_URL: https://portal-ng-ui. + CYPRESS_PORTAL_NG_USERNAME: onap-admin + CYPRESS_PORTAL_NG_PASSWORD: password + + tests: + basicCds: + enabled: true + testName: basic-cds + basicCnfMacro: + enabled: true + testName: basic-cnf-macro + basicCps: + enabled: true + testName: basic-cps + basicOnboard: + enabled: true + testName: basic-onboard + basicNetwork: + enabled: false + testName: basic-network + basicSdnc: + enabled: true + testName: basic-sdnc + basicStatus: + enabled: true + testName: basic-status + fullStatus: + enabled: true + testName: full-status + resultSummary: + enabled: true + testName: result-summary + vesPublish: + enabled: true + testName: ves-publish + pnfMacro: + enabled: true + testName: pnf-macro + controlPanelSmokeTest: + enabled: false + testName: control-panel-ui-smoke-test + aaiInitialDataSetup: + enabled: true + testName: aai-initial-data-setup + serviceWithoutResource: + enabled: true + testName: service-without-resource + pnfWithoutVesEvent: + enabled: true + testName: pnf-without-ves-event + pnfWithVesEvent: + enabled: true + testName: pnf-with-ves-event + addDeletePnfInRunningService: + enabled: true + testName: add-delete-pnf-in-running-service + basicPrh: + enabled: true + testName: basic-prh + checkTimeSync: + enabled: true + testName: check-time-sync + basicKafka: + enabled: true + testName: basic-kafka + addDeleteCnfMacro: + enabled: true + testName: add-delete-cnf-macro + policyFramework: + enabled: true + testName: policy-framework + smokeTests: + artifacts: + storageClassName: + execution: + activeDeadlineSeconds: 1800 + executor: + pythonsdk: + name: pythonsdk-tests-basic-executor + type: pythonsdk-tests/smoke-test + image: /onap/xtesting-smoke-usecases-pythonsdk:master + controlPanelSdk: + name: control-panel-basic-executor + type: controlpanel-sdk/smoke-test + imageVersion: 3.1 + testsuite: + name: onap-testsuite + cron: 0 6 * * * + # Testsuite job is going to be killed after 6 hours + # if it doesn't end by itself. That prevents an issue + # with blocked cronjobs executions + testsuiteJobActiveDeadlineSeconds: 21600 + slackNotifications: + enabled: false + slackConfig: + baseUrl: https://slack.com + token: example + channel: test + +testkube: + testkube-dashboard: + apiServerEndpoint: "https://testkube-api." + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - CAP_NET_RAW + readOnlyRootFilesystem: false + runAsGroup: 65533 + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: RuntimeDefault + podSecurityContext: + fsGroup: 65533 + runAsGroup: 65533 + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: RuntimeDefault + analyticsEnabled: false + preUpgradeHook: + enabled: false + serviceAccount: + create: false + preUpgradeHookNATS: + labels: + sidecar.istio.io/inject: "false" + testkube-api: + image: + registry: + analyticsEnabled: false + minio: + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - CAP_NET_RAW + readOnlyRootFilesystem: true + runAsGroup: 65533 + runAsNonRoot: false + runAsUser: 0 + seccompProfile: + type: RuntimeDefault + podSecurityContext: + runAsGroup: 65533 + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: RuntimeDefault + storageClassName: + image: + registry: + nats: + uri: nats://testkube-nats + storage: "30Gi" + storage: + expriation: + "7" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - CAP_NET_RAW + readOnlyRootFilesystem: true + runAsGroup: 65533 + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: RuntimeDefault + podSecurityContext: + runAsGroup: 65533 + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: RuntimeDefault + testkube-operator: + webhook: + patch: + enabled: true + labels: + sidecar.istio.io/inject: "false" + image: + registry: + migrate: + image: + registry: + preUpgrade: + image: + registry: + labels: + sidecar.istio.io/inject: "false" + proxy: + image: + registry: + resources: + limits: + cpu: 400m + memory: 500Mi + requests: + cpu: 10m + memory: 150Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - CAP_NET_RAW + readOnlyRootFilesystem: true + podSecurityContext: + runAsGroup: 65533 + runAsNonRoot: true + runAsUser: 100 + seccompProfile: + type: RuntimeDefault + mongodb: + storageClass: + image: + registry: + readinessProbe: + timeoutSeconds: 50 + livenessProbe: + timeoutSeconds: 50 + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - CAP_NET_RAW + readOnlyRootFilesystem: false + seccompProfile: + type: RuntimeDefault + podSecurityContext: + fsGroup: 1001 + seccompProfile: + type: RuntimeDefault + nats: + config: + jetstream: + fileStore: + pvc: + storageClassName: + resolver: + pvc: + storageClassName: + container: + image: + registry: + podTemplate: + merge: + spec: + securityContext: + seccompProfile: + type: RuntimeDefault + natsBox: + container: + image: + registry: + merge: + resources: + limits: + cpu: 400m + memory: 500Mi + requests: + cpu: 10m + memory: 150Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - AUDIT_WRITE + - CHOWN + - DAC_OVERRIDE + - FOWNER + - FSETID + - KILL + - MKNOD + - NET_BIND_SERVICE + - SETFCAP + - SETGID + - SETPCAP + - SETUID + - SYS_CHROOT + drop: + - ALL + - CAP_NET_RAW + readOnlyRootFilesystem: false + runAsGroup: 65533 + runAsNonRoot: false + runAsUser: 0 + podTemplate: + merge: + spec: + securityContext: + runAsGroup: 65533 + runAsNonRoot: false + runAsUser: 0 + seccompProfile: + type: RuntimeDefault + reloader: + image: + registry: + merge: + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - CAP_NET_RAW + readOnlyRootFilesystem: false + runAsNonRoot: false + runAsUser: 0 + seccompProfile: + type: RuntimeDefault + container: + merge: + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - CAP_NET_RAW + privileged: false + readOnlyRootFilesystem: false + runAsGroup: 0 + runAsUser: 0 + seccompProfile: + type: RuntimeDefault + +# this is here only temporarily +config: + run_type: "core" + deployment_name: "oom" + deploy_scenario: "onap-nofeature-noha" + node_name: foo + build_tag: bar + res_local_path: "/var/lib/xtesting/results" diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/aai_initial_data_setup/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/aai_initial_data_setup/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/aai_initial_data_setup/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/aai_initial_data_setup/aai_initial_data_setup_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/aai_initial_data_setup/aai_initial_data_setup_configuration.py new file mode 100644 index 0000000000..b23c7e3022 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/aai_initial_data_setup/aai_initial_data_setup_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.aai_initial_data_setup_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_delete_cnf_macro/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_delete_cnf_macro/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_delete_cnf_macro/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_delete_cnf_macro/add_delete_cnf_macro_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_delete_cnf_macro/add_delete_cnf_macro_configuration.py new file mode 100644 index 0000000000..a2baa455ff --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_delete_cnf_macro/add_delete_cnf_macro_configuration.py @@ -0,0 +1,4 @@ +from onaptests.configuration.add_delete_cnf_macro_settings import * +from global_tests_settings import * + +SERVICE_INSTANCE_NAME = f"add_delete_cnf_macro_{str(uuid4())}" diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_pnf_in_running_service/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_pnf_in_running_service/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_pnf_in_running_service/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_pnf_in_running_service/add_pnf_in_running_service_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_pnf_in_running_service/add_pnf_in_running_service_configuration.py new file mode 100644 index 0000000000..5e5efac64a --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/add_pnf_in_running_service/add_pnf_in_running_service_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.instantiate_pnf_without_registration_event_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cds/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cds/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cds/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cds/basic_cds_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cds/basic_cds_configuration.py new file mode 100644 index 0000000000..e5dd80208b --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cds/basic_cds_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.cds_resource_resolution_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cnf_macro/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cnf_macro/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cnf_macro/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cnf_macro/basic_cnf_macro_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cnf_macro/basic_cnf_macro_configuration.py new file mode 100644 index 0000000000..ab895a167d --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cnf_macro/basic_cnf_macro_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.basic_cnf_macro_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cps/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cps/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cps/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cps/basic_cps_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cps/basic_cps_configuration.py new file mode 100644 index 0000000000..3802c94081 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_cps/basic_cps_configuration.py @@ -0,0 +1,6 @@ +from onaptests.configuration.basic_cps_settings import * +from global_tests_settings import * + +CHECK_POSTGRESQL = True + +DB_PRIMARY_HOST = "tcp-pgset-primary" diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_kafka/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_kafka/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_kafka/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_kafka/basic_kafka_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_kafka/basic_kafka_configuration.py new file mode 100644 index 0000000000..085cbc668d --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_kafka/basic_kafka_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.basic_kafka_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_network/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_network/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_network/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_network/basic_network_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_network/basic_network_configuration.py new file mode 100644 index 0000000000..8ef891097a --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_network/basic_network_configuration.py @@ -0,0 +1,4 @@ +from onaptests.configuration.basic_network_nomulticloud_settings import * +from global_tests_settings import * + +SDC_CLEANUP = True diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_onboard/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_onboard/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_onboard/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_onboard/basic_onboard_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_onboard/basic_onboard_configuration.py new file mode 100644 index 0000000000..28fc1a01b7 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_onboard/basic_onboard_configuration.py @@ -0,0 +1,4 @@ +from onaptests.configuration.basic_onboard_settings import * +from global_tests_settings import * + +SDC_CLEANUP = True diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_policy/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_policy/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_policy/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_policy/basic_policy_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_policy/basic_policy_configuration.py new file mode 100644 index 0000000000..6196ede989 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_policy/basic_policy_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.basic_policy_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_prh/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_prh/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_prh/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_prh/basic_prh_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_prh/basic_prh_configuration.py new file mode 100644 index 0000000000..2ee04adbdd --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_prh/basic_prh_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.basic_prh_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_sdnc/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_sdnc/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_sdnc/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_sdnc/basic_sdnc_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_sdnc/basic_sdnc_configuration.py new file mode 100644 index 0000000000..67760550fa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_sdnc/basic_sdnc_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.basic_sdnc_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_status/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_status/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_status/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_status/basic_status_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_status/basic_status_configuration.py new file mode 100644 index 0000000000..874e08960b --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/basic_status/basic_status_configuration.py @@ -0,0 +1,13 @@ +from onaptests.configuration.status_settings import * +from global_tests_settings import * + +STORE_ARTIFACTS = False +CHECK_POD_VERSIONS = False +IGNORE_EMPTY_REPLICAS = True + +WAIVER_LIST = ['integration', 'jaeger', 'performance-test', 'medusa-purge', 'wiremock', 'sample-rapp', '-scraper', 'soak', 'repo1-full'] + +EXCLUDE_NAMESPACE_LIST = ['nonrtric-rapp', 'kyverno', 'cluster-observability'] + +CHECK_ALL_NAMESPACES = True +LOG_CONFIG["handlers"]["file"]["level"] = "INFO" diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/check_time_sync/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/check_time_sync/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/check_time_sync/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/check_time_sync/check_time_sync_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/check_time_sync/check_time_sync_configuration.py new file mode 100644 index 0000000000..52e504d4d7 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/check_time_sync/check_time_sync_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.check_time_sync_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/connectivity.json b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/connectivity.json new file mode 100644 index 0000000000..d3fa0019f2 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/connectivity.json @@ -0,0 +1,6 @@ +{ +"cloud-region":"k8sregion-cnf-macro", +"cloud-owner":"basiccnf-cloud-owner", +"other-connectivity-list": + {"connectivity-records":[]} +} diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/full_status/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/full_status/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/full_status/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/full_status/full_status_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/full_status/full_status_configuration.py new file mode 100644 index 0000000000..c65a67437f --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/full_status/full_status_configuration.py @@ -0,0 +1,4 @@ +from onaptests.configuration.status_settings import * +from global_tests_settings import * + +IGNORE_EMPTY_REPLICAS = True diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/global_tests_settings.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/global_tests_settings.py new file mode 100644 index 0000000000..71cd64ad15 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/global_tests_settings.py @@ -0,0 +1,41 @@ +from os import getenv + +K8S_TESTS_NAMESPACE = getenv("NAMESPACE", "onap") + +CDS_URL = f"http://cds-blueprints-processor-http.{K8S_TESTS_NAMESPACE}.svc.cluster.local:8080" +SDC_BE_URL = f"http://sdc-be.{K8S_TESTS_NAMESPACE}.svc.cluster.local:8080" +SDC_FE_URL = f"http://sdc-fe.{K8S_TESTS_NAMESPACE}.svc.cluster.local:8181" +SO_URL = f"http://so.{K8S_TESTS_NAMESPACE}.svc.cluster.local:8080" +K8SPLUGIN_URL = f"http://multicloud-k8s.{K8S_TESTS_NAMESPACE}.svc.cluster.local:9015" +AAI_URL = f"http://aai.{K8S_TESTS_NAMESPACE}.svc.cluster.local:80" +CPS_URL = f"http://cps-core.{K8S_TESTS_NAMESPACE}.svc.cluster.local:8080" +SDNC_URL = f"http://sdnc.{K8S_TESTS_NAMESPACE}.svc.cluster.local:8282" +TESTKUBE_URL = f"http://testkube-api-server.{K8S_TESTS_NAMESPACE}.svc.cluster.local:8088" +VES_URL = f"http://dcae-ves-collector.{K8S_TESTS_NAMESPACE}.svc.cluster.local:8080" +NBI_URL = f"http://nbi.{K8S_TESTS_NAMESPACE}.svc.cluster.local:8080" +POLICY_API_URL = f"http://policy-api.{K8S_TESTS_NAMESPACE}.svc.cluster.local:6969" +POLICY_PAP_URL = f"http://policy-pap.{K8S_TESTS_NAMESPACE}.svc.cluster.local:6969" +POLICY_PDP_URL = f"http://policy-xacml-pdp.{K8S_TESTS_NAMESPACE}.svc.cluster.local:6969" + +IN_CLUSTER = True +SERVICE_DISTRIBUTION_NUMBER_OF_TRIES = 15 +EXPOSE_SERVICES_NODE_PORTS = False +CPS_AUTH = ("cpsuser", "tj61KoH9") +SDC_CLEANUP = False +#SDNC_DB_PRIMARY_HOST = f"sdnc-db.{K8S_TESTS_NAMESPACE}.svc.cluster.local" +SDNC_DB_PRIMARY_HOST = f"mariadb-galera.{K8S_TESTS_NAMESPACE}.svc.cluster.local" + +AAI_API_VERSION = "v29" + +SDC_SERVICE_DISTRIBUTION_COMPONENTS = [ + "SO-sdc-controller", + "aai-model-loader", + "sdnc-sdc-listener", + "multicloud-k8s" +] + +SDC_SERVICE_DISTRIBUTION_DESIRED_STATE = { + "SO-sdc-controller": "DOWNLOAD_OK", + "aai-model-loader": "DOWNLOAD_OK", + "sdnc-sdc-listener": "DOWNLOAD_OK", +} diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_pnf_without_registration_event/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_pnf_without_registration_event/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_pnf_without_registration_event/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_service_without_resource/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_service_without_resource/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_service_without_resource/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_service_without_resource/instantiate_service_without_resource_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_service_without_resource/instantiate_service_without_resource_configuration.py new file mode 100644 index 0000000000..13e348694a --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/instantiate_service_without_resource/instantiate_service_without_resource_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.instantiate_service_without_resource_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_macro/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_macro/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_macro/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_macro/pnf_macro_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_macro/pnf_macro_configuration.py new file mode 100644 index 0000000000..c67553d2a2 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_macro/pnf_macro_configuration.py @@ -0,0 +1,6 @@ +from onaptests.configuration.pnf_macro_settings import * +from global_tests_settings import * + +USE_SIMULATOR = True +PNF_SIMULATOR_URL = "pnf-macro-test-simulator.onap-tests" +PNF_SIMULATOR_PORT = "5000" diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_with_ves_event/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_with_ves_event/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_with_ves_event/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_with_ves_event/pnf_with_ves_event_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_with_ves_event/pnf_with_ves_event_configuration.py new file mode 100644 index 0000000000..e0e3fe93ec --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/pnf_with_ves_event/pnf_with_ves_event_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.pnf_with_ves_event_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/test-config.yaml b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/test-config.yaml new file mode 100644 index 0000000000..4b9354dd00 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/test-config.yaml @@ -0,0 +1,36 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: v1 +kind: Config +current-context: default +contexts: +- name: default + context: + cluster: cluster + user: cluster-admin + namespace: default +clusters: +- name: cluster + cluster: + insecure-skip-tls-verify: true + server: https://kubernetes.default.svc.cluster.local +users: +- name: cluster-admin + user: + token: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFwR0gwMGl4Q2hpRkU1OHAwSHQydDBMMjZkWk9nLVBmQ1Nfb2NWYjVXVFUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJvbmFwIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tejJzcXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImJjZGVjZTNmLTY2OTQtNDk2Yi05ZjVkLWNmMDA2OTY1NWQ5ZiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpvbmFwOmRlZmF1bHQifQ.DCDab0Ccsj4kTynjKGRNGJrvkB-ZwBKWrJS72596S8ytLx-Ixe-lBxn_zAY3RCuamXASG93MaJQBbv1c_3KK5qf_zgqYoj21xI1A-WeBc_d0uoGtDq6LpgjJ-kmmZ8RE1p6kYIRp5xx-m9rE7jWcMBpxkTKeuZghX4zWwXXKpYzJ9JRW9dZqfRGyEzd32Rx8PlVU9B1G2-I4FInRsNjjD1h-ChR0Ur8mXj0WVJsM8EankmvI7hyDEnbj_DUnw09MhJLGxWyo-HBvj67grQGLpCnQpPZ3_fvWDCnqrv13EXLI_yBRt4rODIe-jFyeTKXV4Krvv8sR01UY1aSoWWh5ZQ diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/ves_publish/__init__.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/ves_publish/__init__.py new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/ves_publish/__init__.py @@ -0,0 +1 @@ + diff --git a/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/ves_publish/ves_publish_configuration.py b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/ves_publish/ves_publish_configuration.py new file mode 100644 index 0000000000..9d810f93a8 --- /dev/null +++ b/argo/onap-test/testkube/pythonsdk-tests/basic_configuration_settings/ves_publish/ves_publish_configuration.py @@ -0,0 +1,2 @@ +from onaptests.configuration.ves_publish_settings import * +from global_tests_settings import * diff --git a/argo/onap-test/trivy-operator.yaml b/argo/onap-test/trivy-operator.yaml new file mode 100644 index 0000000000..d36a332368 --- /dev/null +++ b/argo/onap-test/trivy-operator.yaml @@ -0,0 +1,52 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: trivy-operator + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: https://aquasecurity.github.io/helm-charts + chart: trivy-operator + targetRevision: 0.27.0 + helm: + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap-test/values/trivy-operator.yaml + destination: + server: https://kubernetes.default.svc + namespace: trivy-system + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: disabled + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap-test/values/kafka-ui.yaml b/argo/onap-test/values/kafka-ui.yaml new file mode 100644 index 0000000000..c64876c639 --- /dev/null +++ b/argo/onap-test/values/kafka-ui.yaml @@ -0,0 +1,35 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +existingSecret: "strimzi-kafka-admin" + +yamlApplicationConfig: + kafka: + clusters: + - name: yaml + bootstrapServers: onap-strimzi-kafka-bootstrap:9092 + properties: + security.protocol: SASL_PLAINTEXT + sasl.mechanism: SCRAM-SHA-512 + sasl.jaas.config: "${sasl.jaas.config}" + auth: + type: disabled + management: + health: + ldap: + enabled: false diff --git a/argo/onap-test/values/onap-test-ingress.yaml b/argo/onap-test/values/onap-test-ingress.yaml new file mode 100644 index 0000000000..ed43abd81e --- /dev/null +++ b/argo/onap-test/values/onap-test-ingress.yaml @@ -0,0 +1,20 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +post_addr: "" +dns_zone: "" diff --git a/argo/onap-test/values/testkube.yaml b/argo/onap-test/values/testkube.yaml new file mode 100644 index 0000000000..ce85d2e7a2 --- /dev/null +++ b/argo/onap-test/values/testkube.yaml @@ -0,0 +1,98 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + imageRegistry: + imagePullSecrets: + - artifactory-docker-secret + defaultStorageClass: + storageClass: + serviceMesh: + enabled: true + ingress: + post_addr: "" + dns_zone: "" + +serviceMesh: + envVariable: + SERVICE_MESH_ENABLED: true + +imagePullSecrets: + - name: artifactory-docker-secret + +tests: + testEnvName: "" + smokeTests: + artifacts: + storageClassName: + testsuite: + name: onap-testsuite + cron: 0 6 * * * + # Testsuite job is going to be killed after 6 hours + # if it doesn't end by itself. That prevents an issue + # with blocked cronjobs executions + testsuiteJobActiveDeadlineSeconds: 21600 + # Tests listed below will be included into testsuite + # Important: test here is no a test name but a key from `tests.tests` value dictionary + # We are going to range through list below, get object from `tests.tests` dictionary, + # verify if it's enabled and then add it into testsuite. So user at the end has to + # remember only on one place to enable/disable test. But thanks to that we are able + # to modify order, presence of tests on testsuite but also to include some tests + # which are not a part of given helm package (so for example if that helm is a dependency + # of other package) + tests: + - basicCps + - basicOnboard + - basicNetwork + - basicCds + - basicSdnc + - basicCnfMacro + - controlPanelSmokeTest + - aaiInitialDataSetup + - serviceWithoutResource + - addDeletePnfInRunningService + - pnfWithVesEvent + - pnfWithoutVesEvent + - pnfMacro + - basicPrh + - checkTimeSync + - basicStatus + - basicKafka + - addDeleteCnfMacro + - policyFramework + - vesPublish + +testkube: + testkube-dashboard: + apiServerEndpoint: "https://testkube-api." + testkube-api: + minio: + storageClassName: + image: + registry: + mongodb: + storageClass: + nats: + config: + jetstream: + fileStore: + pvc: + storageClassName: + resolver: + pvc: + storageClassName: diff --git a/argo/onap-test/values/trivy-operator.yaml b/argo/onap-test/values/trivy-operator.yaml new file mode 100644 index 0000000000..f92f8709ff --- /dev/null +++ b/argo/onap-test/values/trivy-operator.yaml @@ -0,0 +1,71 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +# -- targetNamespace defines where you want trivy-operator to operate. By +# default, it's a blank string to select all namespaces, but you can specify +# another namespace, or a comma separated list of namespaces. +#targetNamespaces: "onap" +targetNamespaces: "onap" +operator: + # -- the flag to enable vulnerability scanner + vulnerabilityScannerEnabled: true + # -- the flag to enable sbom generation, required for enabling ClusterVulnerabilityReports + sbomGenerationEnabled: false + # -- the flag to enable cluster sbom cache generation + clusterSbomCacheEnabled: false + # -- scannerReportTTL the flag to set how long a report should exist. "" means that the ScannerReportTTL feature is disabled + scannerReportTTL: "24h" + # -- cacheReportTTL the flag to set how long a cluster sbom report should exist. "" means that the cacheReportTTL feature is disabled + cacheReportTTL: "120h" + # -- configAuditScannerEnabled the flag to enable configuration audit scanner + configAuditScannerEnabled: false + # -- rbacAssessmentScannerEnabled the flag to enable rbac assessment scanner + rbacAssessmentScannerEnabled: false + # -- infraAssessmentScannerEnabled the flag to enable infra assessment scanner + infraAssessmentScannerEnabled: true + # -- clusterComplianceEnabled the flag to enable cluster compliance scanner + clusterComplianceEnabled: true + # -- batchDeleteLimit the maximum number of config audit reports deleted by the operator when the plugin's config has changed. + batchDeleteLimit: 10 + # -- vulnerabilityScannerScanOnlyCurrentRevisions the flag to only create vulnerability scans on the current revision of a deployment. + vulnerabilityScannerScanOnlyCurrentRevisions: true + # -- configAuditScannerScanOnlyCurrentRevisions the flag to only create config audit scans on the current revision of a deployment. + configAuditScannerScanOnlyCurrentRevisions: true + # -- batchDeleteDelay the duration to wait before deleting another batch of config audit reports. + batchDeleteDelay: 10s + # -- accessGlobalSecretsAndServiceAccount The flag to enable access to global secrets/service accounts to allow `vulnerability scan job` to pull images from private registries + accessGlobalSecretsAndServiceAccount: true + # -- builtInTrivyServer The flag enables the usage of built-in trivy server in cluster. It also overrides the following trivy params with built-in values + # trivy.mode = ClientServer and serverURL = http://.:4975 + builtInTrivyServer: false + # -- builtInServerRegistryInsecure is the flag to enable insecure connection from the built-in Trivy server to the registry. + builtInServerRegistryInsecure: false + +image: + registry: +trivyOperator: + skipResourceByLabels: "test-name" +trivy: + resources: + requests: + cpu: 100m + memory: 100M + # ephemeralStorage: "2Gi" + limits: + cpu: 1 + memory: 2000M diff --git a/argo/onap/a1policymanagement.yaml b/argo/onap/a1policymanagement.yaml new file mode 100644 index 0000000000..327a8b92b2 --- /dev/null +++ b/argo/onap/a1policymanagement.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-a1policymanagement + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: a1policymanagement + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/a1policymanagement.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/aai.yaml b/argo/onap/aai.yaml new file mode 100644 index 0000000000..913df22206 --- /dev/null +++ b/argo/onap/aai.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-aai + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: aai + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/aai.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/app-onap.yaml b/argo/onap/app-onap.yaml new file mode 100644 index 0000000000..5f4a882ae7 --- /dev/null +++ b/argo/onap/app-onap.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io + labels: + name: onap +spec: + project: argo-management + source: + repoURL: '' + targetRevision: + path: ./argo/onap + destination: + server: https://kubernetes.default.svc + namespace: onap + syncPolicy: + automated: + prune: false + selfHeal: true + allowEmpty: false + syncOptions: + - Validate=true + - CreateNamespace=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + revisionHistoryLimit: 10 diff --git a/argo/onap/authentication.yaml b/argo/onap/authentication.yaml new file mode 100644 index 0000000000..96ee57b0f1 --- /dev/null +++ b/argo/onap/authentication.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-authentication + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: authentication + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/authentication.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/cds.yaml b/argo/onap/cds.yaml new file mode 100644 index 0000000000..04698bd2cd --- /dev/null +++ b/argo/onap/cds.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-cds + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: cds + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/cds.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/common/cassandra.yaml b/argo/onap/common/cassandra.yaml new file mode 100644 index 0000000000..93860ac37f --- /dev/null +++ b/argo/onap/common/cassandra.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-cassandra + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: cassandra + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/cassandra.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/common/mariadb-galera.yaml b/argo/onap/common/mariadb-galera.yaml new file mode 100644 index 0000000000..ae34fc95fd --- /dev/null +++ b/argo/onap/common/mariadb-galera.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-mariadb-galera + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: mariadb-galera + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/mariadb-galera.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/common/postgres.yaml b/argo/onap/common/postgres.yaml new file mode 100644 index 0000000000..dd12246105 --- /dev/null +++ b/argo/onap/common/postgres.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-postgres + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: postgres + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/postgres.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/common/repository-wrapper.yaml b/argo/onap/common/repository-wrapper.yaml new file mode 100644 index 0000000000..07712fcf17 --- /dev/null +++ b/argo/onap/common/repository-wrapper.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-repository-wrapper + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: repository-wrapper + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/repository-wrapper.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/common/roles-wrapper.yaml b/argo/onap/common/roles-wrapper.yaml new file mode 100644 index 0000000000..007151d653 --- /dev/null +++ b/argo/onap/common/roles-wrapper.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-roles-wrapper + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: roles-wrapper + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/roles-wrapper.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/cps.yaml b/argo/onap/cps.yaml new file mode 100644 index 0000000000..aa1f46d38c --- /dev/null +++ b/argo/onap/cps.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-cps + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: cps + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/cps.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/dcaegen2-services.yaml b/argo/onap/dcaegen2-services.yaml new file mode 100644 index 0000000000..64e5594dba --- /dev/null +++ b/argo/onap/dcaegen2-services.yaml @@ -0,0 +1,52 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-dcaegen2-services + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: dcaegen2-services + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/dcaegen2-services.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/kustomization.yaml b/argo/onap/kustomization.yaml new file mode 100644 index 0000000000..4953d57e3d --- /dev/null +++ b/argo/onap/kustomization.yaml @@ -0,0 +1,41 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - a1policymanagement.yaml + - aai.yaml + - authentication.yaml + - cds.yaml + - cps.yaml + - dcaegen2-services.yaml + - multicloud.yaml + - platform.yaml + - policy.yaml + - portal-ng.yaml + - sdc.yaml + - sdnc.yaml + - so.yaml + - uui.yaml + - strimzi.yaml + - common/cassandra.yaml + - common/mariadb-galera.yaml + - common/postgres.yaml + - common/repository-wrapper.yaml + - common/roles-wrapper.yaml diff --git a/argo/onap/multicloud.yaml b/argo/onap/multicloud.yaml new file mode 100644 index 0000000000..8fdd1bf1a5 --- /dev/null +++ b/argo/onap/multicloud.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-multicloud + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: multicloud + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/multicloud.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/platform.yaml b/argo/onap/platform.yaml new file mode 100644 index 0000000000..1aef4f2d82 --- /dev/null +++ b/argo/onap/platform.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-platform + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: platform + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/platform.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/policy.yaml b/argo/onap/policy.yaml new file mode 100644 index 0000000000..5a3f26491f --- /dev/null +++ b/argo/onap/policy.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-policy + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: policy + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/policy.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/portal-ng.yaml b/argo/onap/portal-ng.yaml new file mode 100644 index 0000000000..3cadc5ca3c --- /dev/null +++ b/argo/onap/portal-ng.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-portal-ng + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: portal-ng + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/portal-ng.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/sdc.yaml b/argo/onap/sdc.yaml new file mode 100644 index 0000000000..1f825f65e4 --- /dev/null +++ b/argo/onap/sdc.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-sdc + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: sdc + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/sdc.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/sdnc.yaml b/argo/onap/sdnc.yaml new file mode 100644 index 0000000000..75122a0d5f --- /dev/null +++ b/argo/onap/sdnc.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-sdnc + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: sdnc + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/sdnc.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/so.yaml b/argo/onap/so.yaml new file mode 100644 index 0000000000..a144b8b431 --- /dev/null +++ b/argo/onap/so.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-so + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: so + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/so.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/strimzi.yaml b/argo/onap/strimzi.yaml new file mode 100644 index 0000000000..a56fa32562 --- /dev/null +++ b/argo/onap/strimzi.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-strimzi + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: strimzi + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/strimzi.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/uui.yaml b/argo/onap/uui.yaml new file mode 100644 index 0000000000..db98a9214c --- /dev/null +++ b/argo/onap/uui.yaml @@ -0,0 +1,51 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: onap-uui + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: onap + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: uui + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/uui.yaml + syncPolicy: + managedNamespaceMetadata: + labels: + istio-injection: enabled + syncOptions: + - CreateNamespace=true + automated: + prune: true + selfHeal: true diff --git a/argo/onap/values/aai.yaml b/argo/onap/values/aai.yaml new file mode 100644 index 0000000000..2b824ea6c7 --- /dev/null +++ b/argo/onap/values/aai.yaml @@ -0,0 +1,50 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +aai-traversal: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 + +aai-resources: + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 + +aai-modelloader: + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 + +aai-babel: + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 + +aai-schema-service: + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 diff --git a/argo/onap/values/authentication.yaml b/argo/onap/values/authentication.yaml new file mode 100644 index 0000000000..cb4a8f1d40 --- /dev/null +++ b/argo/onap/values/authentication.yaml @@ -0,0 +1,21 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + # storageClass for oauth2-proxy setting for Redis DB + defaultStorageClass: diff --git a/argo/onap/values/cassandra.yaml b/argo/onap/values/cassandra.yaml new file mode 100644 index 0000000000..2a3c686dd2 --- /dev/null +++ b/argo/onap/values/cassandra.yaml @@ -0,0 +1,21 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +k8ssandraOperator: + persistence: + storageClassName: diff --git a/argo/onap/values/cds.yaml b/argo/onap/values/cds.yaml new file mode 100644 index 0000000000..0992d5378c --- /dev/null +++ b/argo/onap/values/cds.yaml @@ -0,0 +1,22 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +mariadb-galera: + mariadbOperator: + persistence: + storageClassName: diff --git a/argo/onap/values/cps.yaml b/argo/onap/values/cps.yaml new file mode 100644 index 0000000000..009fc19a88 --- /dev/null +++ b/argo/onap/values/cps.yaml @@ -0,0 +1,23 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +cps-core: + postgres-init: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation diff --git a/argo/onap/values/mariadb-galera.yaml b/argo/onap/values/mariadb-galera.yaml new file mode 100644 index 0000000000..39634aedfc --- /dev/null +++ b/argo/onap/values/mariadb-galera.yaml @@ -0,0 +1,21 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +mariadbOperator: + persistence: + storageClassName: diff --git a/argo/onap/values/multicloud.yaml b/argo/onap/values/multicloud.yaml new file mode 100644 index 0000000000..72621163d9 --- /dev/null +++ b/argo/onap/values/multicloud.yaml @@ -0,0 +1,21 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + # setting for mongodb + storageClass: diff --git a/argo/onap/values/platform.yaml b/argo/onap/values/platform.yaml new file mode 100644 index 0000000000..a3ef1da6da --- /dev/null +++ b/argo/onap/values/platform.yaml @@ -0,0 +1,22 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +cmpv2-cert-provider: + enabled: false +oom-cert-service: + enabled: false diff --git a/argo/onap/values/policy.yaml b/argo/onap/values/policy.yaml new file mode 100644 index 0000000000..63bd36c94e --- /dev/null +++ b/argo/onap/values/policy.yaml @@ -0,0 +1,25 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + mariadbGalera: + localCluster: false + +jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation diff --git a/argo/onap/values/portal-ng.yaml b/argo/onap/values/portal-ng.yaml new file mode 100644 index 0000000000..72621163d9 --- /dev/null +++ b/argo/onap/values/portal-ng.yaml @@ -0,0 +1,21 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +global: + # setting for mongodb + storageClass: diff --git a/argo/onap/values/sdc.yaml b/argo/onap/values/sdc.yaml new file mode 100644 index 0000000000..877dff51f9 --- /dev/null +++ b/argo/onap/values/sdc.yaml @@ -0,0 +1,37 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +sdc-cs: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + +sdc-be: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + +sdc-onboarding-be: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + +sdc-wfd-be: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation diff --git a/argo/onap/values/sdnc.yaml b/argo/onap/values/sdnc.yaml new file mode 100644 index 0000000000..2ccaa71f91 --- /dev/null +++ b/argo/onap/values/sdnc.yaml @@ -0,0 +1,27 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + +network-name-gen: + mariadb-init: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation diff --git a/argo/onap/values/so.yaml b/argo/onap/values/so.yaml new file mode 100644 index 0000000000..1f1261e981 --- /dev/null +++ b/argo/onap/values/so.yaml @@ -0,0 +1,52 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +so-mariadb: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + +so-bpmn-infra: + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 + +so-catalog-db-adapter: + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 + +so-openstack-adapter: + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 + +so-request-db-adapter: + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 + +so-sdc-controller: + podAnnotations: + proxy.istio.io/config: | + tracing: + sampling: 0 diff --git a/argo/onap/values/uui.yaml b/argo/onap/values/uui.yaml new file mode 100644 index 0000000000..2729557680 --- /dev/null +++ b/argo/onap/values/uui.yaml @@ -0,0 +1,32 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +uui-server: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + +uui-intent-analysis: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + +uui-llm-adaptation: + jobAnnotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation diff --git a/argo/onap/values/values-global.yaml b/argo/onap/values/values-global.yaml new file mode 100644 index 0000000000..23adee3dc6 --- /dev/null +++ b/argo/onap/values/values-global.yaml @@ -0,0 +1,196 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2025 Deutsche Telekom +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +################################ +# General configuration of ONAP +# ORIGINAL FILE: +# https://git.onap.org/oom/tree/kubernetes/onap/values.yaml +# +# Using value files in argo: +# https://github.com/argoproj/argo-cd/issues/2789#issuecomment-879043660 +################################ +global: + + # override default resource limit flavor for all charts + flavor: small + + nodePortPrefix: 302 + nodePortPrefixExt: 304 + masterPassword: gatingPassword + addTestingComponents: &testing false + repository: + dockerHubRepository: &dockerHubRepository + elasticRepository: &elasticRepository + quayRepository: + googleK8sRepository: + githubContainerRegistry: + loggingRepository: *elasticRepository + busyboxRepository: *dockerHubRepository + repositoryCred: + user: docker + password: docker + busyboxImage: busybox:1.34.1 + curlImage: curlimages/curl:7.80.0 + envsubstImage: dibi/envsubst:latest + htpasswdImage: xmartlabs/htpasswd:latest + kubectlImage: bitnami/kubectl:1.22.4 + loggingImage: beats/filebeat:5.5.0 + mongodbImage: percona/percona-server-mongodb:7.0.16-10 + mariadbImage: mariadb:11.7.2 + nginxImage: bitnami/nginx:1.21.4 + postgresImage: crunchydata/crunchy-postgres:centos8-13.2-4.6.1 + readinessImage: onap/oom/readiness:6.2.0 + # Default definition of the secret containing the docker image repository + # credentials. In the default ONAP deployment the secret is created by the + # repository-wrapper component, which uses the secrets defined above. + # If this is not wanted or other secrets are created, alternative secret + # names can be used + # Overrides for specific images can be done, if the "image" entry is used as + # a map and the "pullSecrets" is used, e.g. + # image: + # ... + # pullSecrets: + # - myRegistryKeySecretName + # + imagePullSecrets: + - name: '{{ include "common.namespace" . }}-docker-registry-key' + + pullPolicy: Always + jreImage: onap/integration-java11:10.0.0 + clusterName: cluster.local + + # enable this if you have deployed Jaeger alongside ONAP + tracing: + enabled: true + collector: + baseUrl: http://jaeger-collector.istio-system:9411 + sampling: + probability: 1.0 # percentage of requests that are sampled (between 0-1/0%-100%) + + persistence: + mountPath: /dockerdata-nfs + enableDefaultStorageclass: false + parameters: {} + storageclassProvisioner: + volumeReclaimPolicy: Retain + storageClass: "" + debugEnabled: false + passwordStrength: long + + ingress: + enabled: true + # enable all component's Ingress interfaces + enable_all: true + + # Provider: ingress, istio, gw-api + provider: gw-api + # Ingress class (only for provider "ingress"): e.g. nginx, traefik + ingressClass: + # Ingress Selector (only for provider "istio") to match with the + # ingress pod label "istio=ingress" + ingressSelector: ingress + # optional: common used Gateway (for Istio, GW-API) + commonGateway: + name: common-gateway + httpListener: http + httpsListener: https + + # default Ingress base URL and preAddr- and postAddr settings + # Ingress URLs result: + # . + virtualhost: + # Default Ingress base URL + # can be overwritten in component by setting ingress.baseurlOverride + baseurl: "" + # prefix for baseaddr + # can be overwritten in component by setting ingress.preaddrOverride + preaddr: "" + # POSTADDR for baseaddr + # can be overwritten in component by setting ingress.postaddrOverride + postaddr: "" + config: + # All http (port 80) requests via ingress will be redirected + # to port 443 on Ingress controller + # only valid for Istio Gateway (ServiceMesh enabled) + ssl: "redirect" + tls: + secret: 'ingress-tls-secret' + # optional: Namespace of the Istio IngressGateway + # only valid for Istio Gateway (ServiceMesh enabled) + namespace: istio-ingress + serviceMesh: + enabled: true + tls: true + engine: "istio" + nativeSidecars: true + # Global Istio Authorization Policy configuration + authorizationPolicies: + enabled: false + metrics: + enabled: true + custom_resources: false + aafEnabled: false + aafAgentImage: onap/aaf/aaf_agent:2.1.20 + msbEnabled: false + certificate: + default: + renewBefore: 720h0m0s # 30 days + duration: 8760h0m0s # 365 days + subject: + organization: "Linux-Foundation" + country: "US" + locality: "San-Francisco" + province: "California" + organizationalUnit: "ONAP" + issuer: + group: certmanager.onap.org + kind: CMPv2Issuer + name: cmpv2-issuer-onap + cmpv2Enabled: false + platform: + certificates: + clientSecretName: oom-cert-service-client-tls-secret + keystoreKeyRef: keystore.jks + truststoreKeyRef: truststore.jks + keystorePasswordSecretName: oom-cert-service-certificates-password + keystorePasswordSecretKey: password + truststorePasswordSecretName: oom-cert-service-certificates-password + truststorePasswordSecretKey: password + offlineDeploymentBuild: false + centralizedLoggingEnabled: ¢ralizedLogging false + tlsEnabled: false + + # Global flag to enable the creation of default roles instead of using + # common roles-wrapper + createDefaultRoles: true + + # temporarily useOperator is set to false for migration to "Montreal" + mariadbGalera: + # flag to enable the DB creation via mariadb-operator + useOperator: true + # if useOperator set to "true", set "enableServiceAccount to "false" + # as the SA is created by the Operator + enableServiceAccount: false + + # not used in TNAP, as cassandra cluster is not created via ONAP chart + cassandra: + # flag to enable the DB creation via k8ssandra-operator + useOperator: true + # if useOperator set to "true", set "enableServiceAccount to "false" + # as the SA is created by the Operator + enableServiceAccount: false diff --git a/argo/updateVariables.sh b/argo/updateVariables.sh new file mode 100755 index 0000000000..cb972e3112 --- /dev/null +++ b/argo/updateVariables.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# directories to patch +DIRS=("argocd" "infra" "onap" "onap-test") + +# Variables and Replacements (Key=Variable, Value=Replacement) +# Beispiel: VAR1="Wert1", VAR2="Wert2" +declare -A VARS +VARS["ONAP_ARGO_REPO_URL"]="https://git.onap.org/oom" +VARS["ONAP_ARGO_BRANCH"]="master" +VARS["STORAGECLASS"]="cinder-os" +VARS["BASEURL"]="simpledemo.onap.org" +VARS["POSTADDR"]="-test" +VARS["DOCKER_REPO"]="docker.io" +VARS["ONAP_REPO"]="nexus3.onap.org:10001" +VARS["ELASTIC_REPO"]="docker.elastic.co" +VARS["QUAY_REPO"]="quay.io" +VARS["GOOGLE_REPO"]="gcr.io" +VARS["K8S_REPO"]="registry.k8s.io" +VARS["GITHUB_REPO"]="ghcr.io" + +# Funktion to replace in one file +replace_in_file() { + local file="$1" + local tmpfile="${file}.tmp" + + cp "$file" "$tmpfile" + + for var in "${!VARS[@]}"; do + # Replace with value + # -i: inplace, but done with tmpfile, if Backup is required + sed -i "s|<${var}>|${VARS[$var]}|g" "$tmpfile" + done + + mv "$tmpfile" "$file" +} + +# Main Loop: Run through all files in the given directories +for dir in "${DIRS[@]}"; do + # Find all files recursively + find "$dir" -type f | while read -r file; do + replace_in_file "$file" + echo "Done: $file" + done +done + +echo "Done." \ No newline at end of file diff --git a/docs/sections/guides/deployment_guides/oom_argo_release_deploy.rst b/docs/sections/guides/deployment_guides/oom_argo_release_deploy.rst new file mode 100644 index 0000000000..399d96d845 --- /dev/null +++ b/docs/sections/guides/deployment_guides/oom_argo_release_deploy.rst @@ -0,0 +1,451 @@ +.. This work is licensed under a Creative Commons Attribution 4.0 +.. International License. +.. http://creativecommons.org/licenses/by/4.0 +.. Copyright (C) 2025 Deutsche Telekom + +.. Links +.. _ONAP helm release repository: https://nexus3.onap.org/service/rest/repository/browse/onap-helm-release/ +.. _ONAP Release Long Term Roadmap: https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16220234/Long+Term+Release+Roadmap +.. _GitOps Deployment: https://www.cncf.io/blog/2025/06/09/gitops-in-2025-from-old-school-updates-to-the-modern-way/ +.. _Trivy Scan: https://trivy.dev/latest/ +.. _ArgoCD: https://argo-cd.readthedocs.io/en/stable/ +.. _App of Apps: https://argo-cd.readthedocs.io/en/latest/operator-manual/cluster-bootstrapping/ + +.. _oom_argo_release_deploy: + +OOM Deployment using ArgoCD +=========================== + +Besides the deployment of ONAP using helm as described in :ref:`oom_helm_release_repo_deploy`, you +can use GitOps based deployment of ONAP components using ArgoCD or Flux (see `GitOps deployment`_). +This document shows an example for an ArgoCD (see `ArgoCD`_) based installation. + +General principles of GitOps and ArgoCD +--------------------------------------- + +GitOps is a modern approach to continuous delivery and infrastructure management +that uses Git as the source of truth for both application and infrastructure configurations. + +In GitOps, all changes to the system, such as updates or rollbacks, are made through pull +requests in Git repositories, which then trigger automated deployment pipelines. + +This ensures that the environment is always aligned with the desired state defined in the Git +repository, making the system more predictable and auditable. + +ArgoCD is a Kubernetes-native continuous delivery tool that implements GitOps principles. +It monitors Git repositories for changes in configuration files +(such as YAML or Helm charts) and automatically syncs the state of the Kubernetes +clusters to match the desired configuration. +With ArgoCD, users can track application deployments and changes visually through +a web UI or CLI, providing transparency and easy rollback options. +It also supports multi-cluster deployments and offers strong access control mechanisms +to manage who can trigger changes. +The system is highly automated and allows for fast, secure delivery and operational +consistency across environments. + +OOM support for ArgoCD deployment +--------------------------------- + +In the OOM repository a subtree is provided, which contains ArgoCD +Application definitions and other files supporting the installation +using ArgoCD: + +An example structure of the OOM common helm charts is shown below: + +.. code-block:: bash + + argo + ├── argocd + │   ├── app-argocd.yaml + │   ├── kustomization.yaml + │   ├── argo-project.yaml + │   ├── argo-secret.yaml + │   ├── argocd.yaml + │   ├── values + │   │   └── argocd.yaml + ├── infra + │   ├── app-infra.yaml + │   ├── kustomization.yaml + │   ├── certmanager.yaml + │   ├── chartmuseum.yaml + │   ├── compile-onap.yaml + │   ├── ... + │   ├── values + │   │   ├── certmanager.yaml + │   │   ├── chartmuseum.yaml + │   │   ├── compile-onap.yaml + │   │   ├── ... + │   │   └── xxx.yaml + │   ├── compile-onap + │   │   └── helm + │   │      ├── Chart.yaml + │   │      ├── values.yaml + │   │      └── templates + │   │         └── onap-helm-render-job.yaml + │   ├── ... + ├── onap + │   ├── app-onap.yaml + │   ├── kustomization.yaml + │   ├── a1policymanagement.yaml + │   ├── aai.yaml + │   ├── authentication.yaml + │   ├── cds.yaml + │   ├── ... + │   ├── values + │   │   ├── a1policymanagement.yaml + │   │   ├── aai.yaml + │   │   ├── authentication.yaml + │   │   ├── ... + │   │   └── xxx.yaml + ├── onap-test + │   ├── app-onap-test.yaml + │   ├── kustomization.yaml + │   ├── kafka-ui.yaml + │   ├── onap-test-ingress.yaml + │   ├── testkube.yaml + │   ├── trivy-operator.yaml + │   ├── values + │   │   ├── kafka-ui.yaml + │   │   ├── onap-test-ingress.yaml + │   │   ├── testkube.yaml + │   │   └── trivy-operator.yaml + │   ├── ingress-routes + │   │   └── helm + │   │      ├── Chart.yaml + │   │      ├── values.yaml + │   │      └── templates + │   │         └── ingress-kafka-ui.yaml + │   ├── ... + └── update-variables.sh + +The main folders are: + +* argocd + + * Application definition for the ArgoCD deployment + +* infra + + * Application definitions for required infrastructure components + (e.g. Istio, CertManager, DB Operators, ...) + * Required Helm Charts for IngressRoutes, Kiali, ONAP Chart compilation + +* onap + + * Application definitions for ONAP components (e.g. AAI, CDS, SO, ...) + +* onap-test + + * Application definitions for ONAP Test components and tools + (e.g. Trivy Scan, Testkube, Kafka-UI) + +General hints and preparation +----------------------------- + +Prerequisites +^^^^^^^^^^^^^ + +As prerequisite you would need a Kubernetes cluster with the required +capacity to deploy the components into. + +The Infrastructure (e.g. Bare Metal servers, Virtual Hosts) and the +way of deployment (e.g. ClusterAPI, Kubespray) is not restricted. + +In the tests of the OOM team it is done: + +* On a vanilla Openstack cluster +* Using Terraform to create the tenant, VMs and networking +* Using Kubespray to create the K8S cluster +* Use a GitLab-CI pipeline to orchestrate the creation + +At the end of the deployment you need to install ArgoCD in this cluster +for the further process of installation. + +As a input parameters for the ONAP deployment you would need to provide: + +* A local Git(lab) project to store the "argo" Application definitions (or the oom project) +* Storage Class the cluster provides for PVs +* (Optional) A local helm registry to store the ONAP helmcharts, + if you don't use the installed ChartMuseum + +Preparation +^^^^^^^^^^^ + +* Clone the OOM repository into a new Git(Lab) project +* Replace the following variables with the script 'updateVariables.sh' (in argo subdir): + + * with the URL of the new git repo + * with the default K8S storage class + * with the base DNS zone (e.g. "simpledemo.onap.org") + * with the postfix for the hosts (optional) (e.g. "-onap-00") + * URL of the docker repository ('docker.io') + * URL of the ONAP docker repository ('nexus3.onap.org:10001') + * URL of the Elastic docker repository ('docker.elastic.co') + * URL of the Quay.io docker repository ('quay.io') + * URL of the K8S docker repository ('gcr.io') + * URL of the GoogleK8S docker repository ('registry.k8s.io') + * URL of the Github docker repository ('ghcr.io') +* after setting the variables start the script in the argo dir: + './updateVariables.sh' +* check-in the git project + +To allow ArgoCD to access the + +- Git Repository, which contains the application definitions, +- (optional) Helm Repository, which contains the compiled charts + +you need to create secrets to define the repository and the access credentials. +E.g.: + +.. collapse:: argo-secret.yaml + + .. include:: ../../../../argo/argocd/argo-secret.yaml + :code: yaml + +The secrets will be created during the ArgoCD "self-managed" deployment described in the later section. + +General info about the installation of applications +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In this example we use the "App of Apps" Pattern (see `App of Apps`_) to install bundles of applications. +E.g. we create an "onap" application containing multiple ONAP component applications (e.g. so, aai). + +As definition of the "onap" application an "Application" resource is defined, which points to +the directory 'argo/onap' in the examples. + +.. collapse:: app-onap.yaml + + .. include:: ../../../../argo/onap/app-onap.yaml + :code: yaml + +The directory contains a kustomization.yaml file, which contains a resource definition pointing to +the ONAP component application files in its subdirectories. + +.. collapse:: kustomization.yaml + + .. include:: ../../../../argo/onap/kustomization.yaml + :code: yaml + +To add the ONAP application to ArgoCD for management, you can add it via kubectl command:: + + > kubectl apply -f argo/onap/app-onap.yaml + +If you don't want to use the "App of Apps" Pattern, you can also install the single applications, e.g.:: + + > kubectl apply -f argo/onap/so.yaml + +User Guide for ArgoCD example +----------------------------- + +After preparation of the environment and git repository the following steps are executed: + +* Installation of "self-managed" ArgoCD +* Installation of the Infrastructure Applications and compilation and storage of the ONAP charts +* Installation of the ONAP Applications +* Installation of the ONAP Test Applications + +The separation of the deployment steps is done to ease the installation procedure and avoid +dependency problems. But generally it should also be possible to install all applications at once +and let ArgoCD deal with the deployment. + +Installation of "self-managed" ArgoCD +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +After ArgoCD has been installed, you can add an "argocd" application to force ArgoCD to manage itself. + +The definition files can be found in the directory 'argo/argocd': + +* app-argocd.yaml - (AppOfApps-)Application definition file points to the same directory and uses kustomization.yaml +* kustomization.yaml - Kustomize file with resources collection (argocd.yaml, argo-project.yaml, argo-secrets.yaml) +* argocd.yaml - Application definition for ArgoCD +* app-secrets.yaml - Secrets for needed Git/Helm-repositories and credentials +* app-project.yaml - ArgoCD Project definition +* values/argocd.yaml - values definition used by argocd.yaml + +To deploy the ArgoCD "self-managed" (AppOfApps-)application, you can add it via kubectl command:: + + > kubectl apply -f argo/argocd/app-argocd.yaml + +You can now try to access the ArgoCD UI via Port Forwarding of the "argo-service". +The access credentials are "admin/gating" + +Installation of the Infrastructure Applications +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As ONAP requires a number of platform/infrastructure components, the installation of those are bundled in +this "App of Apps" Application. + +The definition files can be found in the directory 'argo/infra': + +* app-infra.yaml - (AppOfApps-)Application definition file points to the same directory and uses kustomization.yaml +* kustomization.yaml - Kustomize file with resources collection for the "App of Apps" Application +* cert-manager.yaml - Application definition for Certificate Manager +* chartmuseum.yaml - Application definition for ChartMuseum (required for compile-onap) +* compile-onap.yaml - Application definition a local helm chart used for local ONAP chart compilation +* compile-onap/helm/* - Helm chart used for local ONAP chart compilation +* django-defectdojo.yaml - Application definition for Defect-Dojo (used as Trivy Report UI) +* gateway-api.yaml - Application definition for Gateway-API CRDs +* gateway-api/* - CRD definitions of Gateway-API +* infra-ingress.yaml - Application definition for a local helm chart for Ingress routes (ingress-routes) +* ingress-routes/helm - Helm chart with ingress definition for Infra Applications and Ingress Gateway +* istio.yaml - Application definition for Istio ServiceMesh +* jaeger.yaml - Application definition for Jaeger +* k8ssandra-operator.yaml - Application definition for K8ssandra-Operator +* keycloak-db.yaml - Application definition for the Database instance for Keycloak +* keycloak.yaml - Application definition for Keycloak +* kiali-operator.yaml - Application definition for the Kiali-Operator +* kiali.yaml - Application definition for the Kiali Instance +* kiali-instance/* - Definition of the Kiali Instance +* mariadb-operator-crds.yaml - Application definition for the MariaDB-Operator CRDs +* mariadb-operator.yaml - Application definition for the MariaDB-Operator +* mongodb-operator.yaml - Application definition for the MongoDB-Operator +* nfs-server-provisioner.yaml - Application definition for the NFS Server Provisioner +* postgres-operator.yaml - Application definition for the Postgres-Operator +* prometheus.yaml - Application definition for the Prometheus +* strimzi.yaml - Application definition for the Strimzi-Kafka-Operator +* trivy-dojo-report-operator.yaml - Application definition for the Trivy-DefectDojo Connector +* values/* - values definition for all infra applications + +To deploy the Infrastructure (AppOfApps-)application, you can add it via kubectl command:: + + > kubectl apply -f argo/infra/app-infra.yaml + +After the successful installation of the Ingress setup you should be able to start the ArgoCD UI via the URL: + +``https://argocd.`` + +Access credentials are "admin/gating" + +.. figure:: ../../resources/images/argocd/login.jpg + :align: right + +You should see in the UI the Application trees of "argo-management" and "infra-components" + +.. figure:: ../../resources/images/argocd/argocd.jpg + :align: right + +.. figure:: ../../resources/images/argocd/infra.jpg + :align: right + +Within the Infrastructure components the "compile-onap" App creates a job, which downloads the "OOM" +git repository, compiles the ONAP charts and stores them into the "ChartMuseum" App. + +The ChartMuseum is used as Helm Repository for the ONAP Applications. +Within the ONAP Application definitions (e.g. in 'argo/onap/aai.yaml') you see as source definition +the internal Chart Museum Service URL ('repoURL'). + +If you want to use another repository, you need to change the value. +The Chart version ('targetRevision') is set as "*", so it uses the latest version it finds. + +If you want, you can specify here a fixed release version (e.g. '16.0.0'). + +.. code-block:: yaml + + apiVersion: argoproj.io/v1alpha1 + kind: Application + metadata: + name: onap-aai + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io + spec: + ... + sources: + - repoURL: '' + targetRevision: + ref: defaultValues + - repoURL: http://chartmuseum.chartmuseum:8080 + chart: aai + targetRevision: "*" + helm: + ignoreMissingValueFiles: true + valueFiles: + - $defaultValues/argo/onap/values/values-global.yaml + - $defaultValues/argo/onap/values/aai.yaml + ... + +Installation of the ONAP Applications +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The deployment of ONAP components is shown here as "App of Apps" application. + +The selection of the ONAP component can be done via the kustomization.yaml file. + +If the "App of Apps" pattern is not wanted, the components an also be deployed individually. + +The definition files can be found in the directory 'argo/onap': + +* app-onap.yaml - (AppOfApps-)Application definition file points to the same directory and uses kustomization.yaml +* kustomization.yaml - Kustomize file with resources collection for the "App of Apps" Application +* a1policymanagement.yaml - Application definition for A1 Policy Management +* aai.yaml - Application definition for AAI component +* authentication.yaml - Application definition for Authentication component +* cds.yaml - Application definition for CDS component +* common/cassandra.yaml - Application definition for the common CASSANDRA DB instance +* common/mariadb-galera.yaml - Application definition for the common MariaDB instance +* common/postgres.yaml - Application definition for the common Postgres DB instance +* common/repository-wrapper.yaml - Application definition for the common Repository Wrapper +* common/roles-wrapper.yaml - Application definition for the common Roles Wrapper (optional) +* cps.yaml - Application definition for CPS component +* dcaegen2-services.yaml - Application definition for DCAEGEN2-SERVICES component +* multicloud.yaml - Application definition for MULTICLOUD component +* platform.yaml - Application definition for PLATFORM component +* policy.yaml - Application definition for POLICY component +* portal-ng.yaml - Application definition for PORTAL-NG component +* sdc.yaml - Application definition for SDC component +* sdnc.yaml - Application definition for SDNC component +* so.yaml - Application definition for SO component +* strimzi.yaml - Application definition for STRIMZI component +* uui.yaml - Application definition for UUI component +* values/* - values definition for all ONAP applications + common values-global.yaml + +To deploy the ONAP (AppOfApps-)application, you can add it via kubectl command:: + + > kubectl apply -f argo/onap/app-onap.yaml + +You should see in the UI the Application trees of "onap" + +.. figure:: ../../resources/images/argocd/onap.jpg + :align: right + +Installation of the ONAP Test Applications +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As we use the ArgoCD deployment also for testing the ONAP components, +we decided to add an application set to deploy testing components. + +The definition files can be found in the directory 'argo/onap-test': + +* app-onap-test.yaml - (AppOfApps-)Application definition file points to the same directory and uses kustomization.yaml +* kustomization.yaml - Kustomize file with resources collection for the "App of Apps" Application +* kafka-ui.yaml - Application definition for Kafka UI +* onap-test-ingress.yaml - Application definition for a local helm chart for Ingress routes (ingress-routes) +* ingress-routes/helm - Helm chart with ingress definition for KAfka UI Application and Ingress Gateway +* testkube.yaml - Application definition for the TESTKUBE Chart deployent for running ONAP tests +* testkube/helm/* - Helm chart for the TESTKUBE application +* testkube/pythonsdk-tests/* - TESTKUBE test definitions based on ONAP PythonSDK +* values/* - values definition for all ONAP Test applications + +To deploy the ONAP-Test (AppOfApps-)application, you can add it via kubectl command:: + + > kubectl apply -f argo/onap-test/app-onap-test.yaml + +URLs of Applications +^^^^^^^^^^^^^^^^^^^^ + +Besides the ONAP applications the following applications are exposed via Ingress: + +* ArgoCD: ``https://argocd.`` (admin/gating) +* Kafka-UI: ``https://kafka-ui.`` +* Cassandra-Reaper: ``https://reaper-dc1.`` (see secret "cassandra-reaper-ui") +* Testkube: ``https://testkube.`` +* DefectDojo: ``https://defectdojo.`` (admin/gating) +* Grafana: ``https://grafana.`` (admin/prom-operator) +* Kiali: ``https://kiali.`` +* Jaeger: ``https://jaeger.`` +* Keycloak: ``https://keycloak-ui.`` (admin/secret) + +ONAP applications follow the same schema, e.g. portal-ng: + +* PortalNG: ``https://portal-ng-ui.`` +* ... diff --git a/docs/sections/guides/deployment_guides/oom_deployment.rst b/docs/sections/guides/deployment_guides/oom_deployment.rst index 21e988da5b..093a5fec54 100644 --- a/docs/sections/guides/deployment_guides/oom_deployment.rst +++ b/docs/sections/guides/deployment_guides/oom_deployment.rst @@ -20,6 +20,7 @@ charts. * :ref:`oom_helm_release_repo_deploy` * :ref:`oom_helm_testing_repo_deploy` * :ref:`oom_dev_testing_local_deploy` + * :ref:`oom_argo_release_deploy` .. warning:: | **Pre-requisites** @@ -34,11 +35,12 @@ See the :ref:`oom_customize_overrides` section for more details. .. toctree:: - :hidden: + :maxdepth: 1 oom_customize_overrides.rst oom_helm_release_repo_deploy.rst oom_helm_testing_repo_deploy.rst oom_dev_testing_local_deploy.rst + oom_argo_release_deploy.rst diff --git a/docs/sections/guides/infra_guides/oom_infra_deployment_requirements.rst b/docs/sections/guides/infra_guides/oom_infra_deployment_requirements.rst index 0de177510e..208c21d453 100644 --- a/docs/sections/guides/infra_guides/oom_infra_deployment_requirements.rst +++ b/docs/sections/guides/infra_guides/oom_infra_deployment_requirements.rst @@ -52,7 +52,7 @@ The versions of software that are supported and tested by OOM are as follows: ============== =========== ======= ======== ======== ============= ======== New Delhi 1.28.6 3.13.1 1.28.x 20.10.x 1.14.4 0.41.0 Oslo 1.28.6 3.13.1 1.30.x 23.0.x 1.16.2 0.44.0 - Paris 1.30.4 3.16.4 1.30.x 23.0.x 1.17.2 0.45.0 + Paris 1.32.5 3.16.4 1.32.x 23.0.x 1.17.2 0.46.0 ============== =========== ======= ======== ======== ============= ======== .. table:: OOM Software Requirements (production) diff --git a/docs/sections/release_notes/release-notes-oslo.rst b/docs/sections/release_notes/release-notes-oslo.rst new file mode 100644 index 0000000000..e64f10fb2a --- /dev/null +++ b/docs/sections/release_notes/release-notes-oslo.rst @@ -0,0 +1,175 @@ +.. This work is licensed under a Creative Commons Attribution 4.0 + International License. +.. http://creativecommons.org/licenses/by/4.0 +.. (c) ONAP Project and its contributors +.. _release_notes_oslo: + +:orphan: + +************************************* +ONAP Operations Manager Release Notes +************************************* + +Previous Release Notes +====================== + +- :ref:`New Delhi ` +- :ref:`Montreal ` +- :ref:`London ` +- :ref:`Kohn ` +- :ref:`Jakarta ` +- :ref:`Istanbul ` +- :ref:`Honolulu ` +- :ref:`Guilin ` +- :ref:`Frankfurt ` +- :ref:`El Alto ` +- :ref:`Dublin ` +- :ref:`Casablanca ` +- :ref:`Beijing ` +- :ref:`Amsterdam ` + +Abstract +======== + +This document provides the release notes for the Oslo release. + +Summary +======= + + + +Release Data +============ + ++--------------------------------------+--------------------------------------+ +| **Project** | OOM | +| | | ++--------------------------------------+--------------------------------------+ +| **Docker images** | N/A | +| | | ++--------------------------------------+--------------------------------------+ +| **Release designation** | Oslo | +| | | ++--------------------------------------+--------------------------------------+ +| **Release date** | 2025/01/09 | +| | | ++--------------------------------------+--------------------------------------+ + +New features +------------ + +* Support the latest Database Operators: + + * MariaDB-Operator (0.36.0) + * K8ssandra-Operator (v0.20.2) + * Postgres-Operator (CrunchyData) (5.7.2) + * MongoDB-Operator (Percona) (1.18.0) + +* authentication (15.0.0) + + * support for REALM Client AuthorizationSettings + * update oauth2-proxy and keycloak-config-cli versions + * add support for latest keycloak version 26.x + +* Update the helm common templates (13.2.10) to: + + * add SecurityContext settings for Production readiness + +* cassandra (13.1.1) + + * support for new cassandra version (4.1.6) + * add SecurityContext settings for Production readiness + +* mariadb-galera (13.2.3) + + * add SecurityContext settings for Production readiness + +* mariadb-init (13.0.2) + + * add SecurityContext settings for Production readiness + +* mongodb (14.12.4) + + * add SecurityContext settings for Production readiness + +* mongodb-init (13.0.2) + + * new chart to support external mongodb initialization + +* postgres (13.1.0) + + * add SecurityContext settings for Production readiness + +* postgres-init (13.0.3) + + * add SecurityContext settings for Production readiness + +* readinessCheck (13.1.1) + + * add SecurityContext settings for Production readiness + +* serviceAccount (13.0.2) + + * adjust default role mapping + +**Bug fixes** + +A list of issues resolved in this release can be found here: +https://lf-onap.atlassian.net/projects/OOM/versions/10783 + +**Known Issues** + + +Deliverables +------------ + +Software Deliverables +~~~~~~~~~~~~~~~~~~~~~ + +OOM provides `Helm charts `_ + +Documentation Deliverables +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- :ref:`Project Description ` - a guide for developers + of OOM +- :ref:`oom_dev_guide` - a guide for developers of OOM +- :ref:`oom_infra_guide` - a guide for those setting up the environments that + OOM will use +- :ref:`oom_deploy_guide` - a guide for those deploying OOM on an existing + cloud +- :ref:`oom_user_guide` - a guide for operators of an OOM instance +- :ref:`oom_access_info_guide` - a guide for operators who require access to + OOM applications + +Known Limitations, Issues and Workarounds +========================================= + +Known Vulnerabilities +--------------------- + + +Workarounds +----------- + +Security Notes +-------------- + +**Fixed Security Issues** + +References +========== + +For more information on the ONAP Istanbul release, please see: + +#. `ONAP Home Page`_ +#. `ONAP Documentation`_ +#. `ONAP Release Downloads`_ +#. `ONAP Wiki Page`_ + + +.. _`ONAP Home Page`: https://www.onap.org +.. _`ONAP Wiki Page`: https://lf-onap.atlassian.net/wiki +.. _`ONAP Documentation`: https://docs.onap.org +.. _`ONAP Release Downloads`: https://git.onap.org +.. _`Gateway-API`: https://istio.io/latest/docs/tasks/traffic-management/ingress/gateway-api/ diff --git a/docs/sections/release_notes/release-notes.rst b/docs/sections/release_notes/release-notes.rst index 161f251a45..2ba12a87dd 100644 --- a/docs/sections/release_notes/release-notes.rst +++ b/docs/sections/release_notes/release-notes.rst @@ -11,6 +11,7 @@ ONAP Operations Manager Release Notes Previous Release Notes ====================== +- :ref:`Oslo ` - :ref:`New Delhi ` - :ref:`Montreal ` - :ref:`London ` @@ -29,13 +30,11 @@ Previous Release Notes Abstract ======== -This document provides the release notes for the Oslo release. +This document provides the release notes for the Paris release. Summary ======= - - Release Data ============ @@ -46,74 +45,82 @@ Release Data | **Docker images** | N/A | | | | +--------------------------------------+--------------------------------------+ -| **Release designation** | Oslo | +| **Release designation** | Paris | | | | +--------------------------------------+--------------------------------------+ -| **Release date** | 2025/01/09 | +| **Release date** | 2025/06/26 | | | | +--------------------------------------+--------------------------------------+ New features ------------ -* Support the latest Database Operators: +* Tested on the latest K8S Infrastructure - * MariaDB-Operator (0.36.0) - * K8ssandra-Operator (v0.20.2) - * Postgres-Operator (CrunchyData) (5.7.2) - * MongoDB-Operator (Percona) (1.18.0) + * Kubernetes (v1.32.5) + * CertManager (1.17.2) + * Istio (v1.26.1) + * Keycloak (26.0.6) -* authentication (15.0.0) +* Support the latest Database Operators: - * support for REALM Client AuthorizationSettings - * update oauth2-proxy and keycloak-config-cli versions - * add support for latest keycloak version 26.x + * MariaDB-Operator (0.38.1) + * K8ssandra-Operator (v1.23.2) + * Postgres-Operator (CrunchyData) (5.8.1) + * MongoDB-Operator (Percona) (1.19.1) + * Strimzi Kafka Operator (0.46.0) -* Update the helm common templates (13.2.10) to: +* Update the helm common templates (13.2.19) to: - * add SecurityContext settings for Production readiness + * Make Jobs GitOps ready + * Fix security vulnerabilities -* cassandra (13.1.1) +* cassandra (16.0.0) - * support for new cassandra version (4.1.6) - * add SecurityContext settings for Production readiness + * Support for new cassandra version (4.1.8) + * Fix security vulnerabilities -* mariadb-galera (13.2.3) +* mariadb-galera (16.0.0) - * add SecurityContext settings for Production readiness + * Support for new mariadb version (11.7.2) + * Fix security vulnerabilities -* mariadb-init (13.0.2) +* mariadb-init (16.0.0) - * add SecurityContext settings for Production readiness + * Use ‘mariadb’ client instead of ‘mysql’ + * Add Job Annotations -* mongodb (14.12.4) +* mongodb (16.5.7) - * add SecurityContext settings for Production readiness + * Use the latest Bitnami charts -* mongodb-init (13.0.2) +* mongodb-init (13.0.6) - * new chart to support external mongodb initialization + * Add Job Annotations + * Harmonize resource labeling -* postgres (13.1.0) +* nginx (18.3.5) - * add SecurityContext settings for Production readiness + * New (Bitnami) Chart used for UUI -* postgres-init (13.0.3) +* postgres-init (13.0.6) - * add SecurityContext settings for Production readiness + * Add Job Annotations + * Harmonize resource labeling -* readinessCheck (13.1.1) +* readinessCheck (13.1.4) - * add SecurityContext settings for Production readiness + * Update to the latest image + * Harmonize resource labeling -* serviceAccount (13.0.2) +* timescaleDB (13.0.2) - * adjust default role mapping + * Harmonize resource labeling **Bug fixes** A list of issues resolved in this release can be found here: -https://lf-onap.atlassian.net/projects/OOM/versions/10783 +https://lf-onap.atlassian.net/projects/OOM/versions/10791 **Known Issues** diff --git a/docs/sections/resources/images/argocd/argocd.jpg b/docs/sections/resources/images/argocd/argocd.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1fa2df25aede2f2dc4d79e3c139fc414ff24f66 GIT binary patch literal 315593 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%0D(uHCR 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�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

-P;6%v_ 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 literal 0 HcmV?d00001 diff --git a/docs/sections/resources/images/argocd/infra.jpg b/docs/sections/resources/images/argocd/infra.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db456350e33e1f4302687949113b2c803b9b4ba9 GIT binary patch literal 420040 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%0A9rJUtTq*Y%=(}F1{TkjZ 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 z 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 z zm?33yBxGieZ7H+f*rUxmrJ8Olja+b-BqWpDJz??H1b`SfH+qf-P}v+gcG%AnoVB2B zKtZI8E)Ab4^V+D-D=?P8_V&GBOQC!u@ih;?c+z71>H#w@d;-s)@{2sz#{6A2ktL(< zSRu7)IMlDaGAqs$o4L{$dq;r9Y^4Ng9k0iGILbGvWDwUyvIq3L3ROKp)XFP;aMN)p z)IwwL$ajM>gL2p7`GcMGo`eJf2sKeDxBSlI-GNIbNw_zJb@D`8*pqc1uT7i9?AK$ELIpIhg^HMuPr`@Gt00`9H?S**N%68j!Gw~uvka6r*&t3_QM@+Dx6&eA$A?7 zz=A}j1YB!}H+of0WmY0XMswZ^+*!UZhV$+q=6W%@at+4087J_q?fD<$&kIn3LT6K< zKc?%_HFbt5=j2Nl-0{^5TGf3_fT0j^-h)xqKH!;hzOG>5^;8q@^-jp5ntN&b@hY!* z?I>#!&^exApi040A=NUl9*xhtLd#9Eg&-zU+a}fyK!T`2&eB|$t;&5gk7V}Ehh!BL z53-Z!A5Gg_Fm)H_1rmV;v3${sEJ(`}5tFwev>mo$#+%%F`@SeD982 zW;B_M(nL@8i@5vNSj#{__MC+`f!jM%^|uX{1Z}ora!p%PQgm6Mnhan}?**%l3(v7- z)c0nDH8Ri*0F6C%6OhqW?eKJ2FTDFU;YzjSP14#RO3Y}$erZX*s61OZKL(cFk;U!? zd*|d0fjfb;^A)}w>`UQu#$<)hw|J2dK>nCHZL9SVAF|+=>?A8TR71R$zc~CzvTpt@ zjt&3NWUX4sO!91ikIa|$r|(ZUn7AZft?ZB>Q|*j%xN4TrVz!V{3r2HS}pqhepq zX>^Q(n*%kQu=Xdj&sY3{4=U&H=MVcyOEw56KfW%Vn=g7y(=kLopfAUclUY3~TjY%l zg~4j(PR}nI+o4I;(>B8;4<*GXrsQ~gTGP+EciqGY+eINTuK6+7f%J^k2%|D(Ywz!0 zR*LCp$h@UTqy|))aCGDva=??Yk5CDr6?}O(ahowAOT1pqKsd+)%)w^8jmu&q1NjqP zL<6w1M&2f9;O!jTz+rXrvWQyRvr&sthA)#HK6dvtxjOypl6NtY&fs-{fxSl818<*K z4Z8$+V>ks7sw_nUzZ^DMiR%o7$2zTS!uq^`kp!;;Ve*($2@iNR{?VrjaY<7vxA|G? zhhe%n|8^wBMMJ3S(9L^nzKlJL&hPUy*TYoYGdDK&qY%@jQnZ_7s95MmA{aqtkomEj zkKo@Uhd~WjK6!G@yPU_LD{fOcNK*z8XOdRKT_WY3S$?oTDm{?`PkuY!WK=s{0igD_ zsfy6KhQl?|L~8hgJ==@I$wlJA=$ssf@2fYFMcJzfSI%@P4xrmvWo7fk@MNfN2fXib z0i)!7sz%w!+}A#)ZxfDayo%T^$DvRiI(B5|AY?0|qTU-a*4-&xiOnTgpuZbtG(AA7 z#(QKQ3QUOl(em>!lKZ9%Jp;uvRvJbcDO%%|ip4=A5 z$~6?b&yvLiY2lhdy)oM2Kws(f?l<-Ns&YZiazCjR$bR^b`q9V7KdSerjd&-@U6^yi zu&whlnURW0FjZOT`I>M5EaRG{J$cr81LY^JN@_nn4F|nx&P7hg0D>nC$==<%+1B>@=Lm>F}P77X^PbZRCsP8QMiV@ol2!fvZ zoD2`(hU?x!v^ZTk?tc^5oXyG(*Q6yG2d80z2i034)$7bFHc_#trmSxQ83RS4E&K5y zFAvYLBX52aP%1H0!EOr~_Hb)a0_yJna3=A8^Wb+6)oPPKVj&8RlD%!>Z7wZyM{ZgMC*D=TA(uE zLf`pvWjmOGIoR^qa*1zbXxhL6<4Hf@_JQJ)j89nxKGg>Umup7kI!Nl&rzxMEEJpp( zlZ&ar*!%?X)L-rLOMxnY<(Z_?j^BKPRuC2$M??P0;^os!)wl88 z>%M#9BQp)Tn2-yk&-7*eD)AF1ekg6uF_h_E=t_!VevwQ$3iFDWYpP~*Y1eU7K0`Q9 zJlXZB8Q+6{TiF2SR2W7eWVM->qq@3u`->}@Vv*X*00UkDcW8sCrlWHCBCH)5R+R>R zJEq?k94`2-`nN7w2iM*O)UrDqF=WE><%4chzXHjWy!ZI&V4M7S;?0els?$!RaXFLP zjTXu6kHQ*kmr!}4B$^1SOeQYNhyel63_f$qUfBw|O~RD1A!(C-;!F3=n<8@-=XktMj4lE>~JQI-RUNJ$uc{9a31<1T-5ncb<>o# z^C&8L*-1?%SyGq1W-N4GllEyr3zh&~>=>%lFvkamHL%UtPnV-ek&0q#=e)z(NZX(k zvdZWC7H)yNKY5NUNhG+ydjhA#hB`^*75b+nC7uTdg8b*}`I39G_Xd$OPKcFn0&e2B z%Nh5~2j^>>g;Ay0%Zu6X3~7c76fNJVr;0ugc6X~HPehjCF8Tqsg{+!9`Qx;NKsvi| z2*$_4KX?-2Z3rxLq!jV8Rl(nUb z_IW*bcu?xO{m?*~1(3qIXP0S$F7**X;S^oB0pQnrGrVaE z92S%CWkQ^H6@M}G%K4yVLR);klEbXyt;ZK`b@+c1=!Ja~poV^dWwGXYY@v+nANYjc zzqMTNhQ{OubaYLTYvG?(9W3pF{IrjqWsx#8Pd14l>tSHu@RyWC8D)+J^j;ATsGLiu?5z4;n_flq%ouWuCezL8%{bO1_z`xSzEO>E4GGV zSU6n+)QM%M|>Q z{z$2Q0Ih-0hW8*(1(NHXo;P1Y(fjpg4lDiUNH-%!g?%b+#Q){*|7)&i@oZDX8or|{ zl5>~;Vj=$hoz>nGeV$ts!9Vs@C3V{xRvh61qk+?VP{1Is3ppKIbi@yrZZhRvQ{^Ja z#n_*c%e^5Ts+3TPCYc#lLCMX{Tw`0!d&bOTM%uZ-k&B;u8a;FmSoTqxh~D2AUip1$Z0qR~m8 z^L{=iHpKu&TqZ;^=fFz0bjt%x*hV^f^9=tT0$Evgg#r&x77{J5=BlI(w>2SU7nc0l zCNbLNR=C;Pl;|UgI8s`myzTRC+OK^6k)O@}{qs8LxBVy&hmo)H9^TA~*P@bjO8DFE zX*yOGsRQz4TAPT&HeXAYripBXgt$ zsP5?|yJV~IXL`WT*>cEauuL3oQ7QzXf(q#2O^M1y-jKg`2nnj0RNu~Eezq+NKHFc} z9x{}6p-UzqRmod{V-JLa7U#a82peYp23A|XC3c{~5zs5X;f8U%g>fd_-B!wb%wlwc z&j(=$pIykJ1)7EQXYvM1zd>z zc|}_@xe@m3i;>e?pr(w?3MG>acc#YM8ayv{*me#3?Bm3gV%Dyd=0*kG%0&Whv3+@P zYrYT9Jc6n_ET4kAseptWEa6Cu%iT2ws!N7J=V43jw3U}1#QVF z_r&~sImKB#LfWQ{;Krw0oQ%VAh+^eq2{WGQ=_1q=WKmO)pA8D+&>;yRq`z&1#BY!t z^RIoma*C3E&-HOe^}(gHlJBi}iaiN0!h6k$%Au0HiviJwEdQhw4PWB2QjZ3>E-DGCGY2$b~clBo=@kRMP@^NjnlKCYC}O5;hHp0e=27V-j`E8fRB>9z>Be zEp&=+B+r5k%m`9540lY4P>I<~0X39>C*}1WF1DTi3mJ1cg1ZZp^HOAhGcBy{1<;8h zv)gb%${9b48fj!14Yq1eklH)Axt)}8oKj5nrUi@a@9V8JX=N?uW2e1q)Af{~p3P+E zbu8}*Vqd<5CtpfG@9vCN6H*hpePw9{W@${+4Ns$cqWyfh$MZ@XktGWXZk^h6Gky6| zJBd78eybUQkSy-R#a!;Fb~E26=WWxxN6)DDH(=b7CyZIq6J}Cmn&?xEf(f4hLcqD$ zMw&Y~9@D$E4TZVJu@3CgXDTAh)tV{T=xPh@c3a6%wZ?KxCrtw-7E3mEQgWtT^#z2c zO9}_E*|{52`GkT_4>WVxO^lqVXqHjK_5Sl@bfsDpdWRlELp5B>g$iPm=<<$N!137l zi#NS{3${a(hr%O@m-bDVqc~z*7Iw#7-bo>@f2nv{H_GMGr!el>L9Yn)BG$yf1={0* zmamDeE^x7J1ADiy4;@)sI}&wSLDBa8)*D5mOFDR+cY@*1RYJmo8ze5BD$0*L10?)hC-&G=wXNOKX*0c|N4{x4!p(`y70ER&C1vb^N2dtIp`8 z#SjOtYdKC1P%Ez1B~`k{u%i9y!VHceE&3D)+}nRgh|9KMZ)6?#0+7l{cR05lS~^y6 zI|PG`8mcR2MJNkEoUaGEK=woYv%c-pb0`_Z9 zN+OF6N$)qZh@##gaP0%HYvpXrN=>i1>f2G-w&i4a7nd}r;^!=`+seoKCcqqHqWp`D zv3Y5~+aLUU3L7uhGc5HA2C0v$hxtweOS`8?-bL4|xvv&+1&8Ucx*nNbN3QK2+d1eN zRNlI2m<-x$+X2841FwEZ82I37e?Y7V`&@fYQen{gqTl)OP`gWHRLu?EgxXluN zT8f;}zxC*j6a{gu&M6BU-+0AzU;f0v;q71JHL^JJ*sqNR2HG_;@lrP%r-{4<3Aivx zZJTtWe7m*u?&vI?IaDL_X7d39nw#CRpCOOh4&YggUsf&R#?T8(ZLSVw%C$b1wk}zI z_PMfApKyEzQ{D*ICpOW-xv_F0D6FLP@G`u^p8j;{l>uV9)xq$(xloJuZHK%=ClU`6 zQ(WhW>K6yWa(F}bMdHsvq)lmvFz@Q`>QU6_SPOtqtN~I=Noej&VMTW_GPwKLf+b4l zk!*!S4)b%NS(B(@sC}J<7OefkG;ZjPg4ck`s9}#^-p2tR?to>k=2%^gHQQ-WM%A9q z00I0d9O}KJ-__fJtz8oos|Cs3wF4-v7eSIv0ftJtqKe&b8z{z;s50P9X@XdS)9w&qWzeg9w2LC|hKpHpkYaf8$f?aln32>PO)YwI zCfb5Pjbw39rmb#0O^)G}WN?bnj0B=WxI&mD-15v0HoxQ35dj+x(KD`!`~g*Ky@fkK zs|T>}b?Y^brHFu_$|$J~u!Uf$ay9oXeyXDTsz4f%b;%O1p5sc z1I&6RoPMb!e_O6-v}btKyW#yh92v4OcJ2v|?x^nXMdr-)HRyhNxybs#FAFt2tY`$;b|+i6)&(_ke>;7?h9`I;j)Is}d; zvyX5n#ig_oJf3)wr!eQ-E~CnjXlFnLhowHkU@Oo{f!EtPXB$`6n1t0z-IfrI`@81Q zVp_xi7nMtCbun<@d8k~z{-F-_Q0SZq`o-cQy~g24wS(s5S3pdfFi z;aK}{gRFOB&cMvIJKEdcXVYR;u5;PCq#8QmnI4KCO@?gfb|x&v-h4j?f|xp`tA1?1 z@~hHW*FSOTI$bcQt!TCMk4v4r*?E8tx5L%j%GSkK7()vYJ~cd6Q{?lTvC5+{{s5YZ zXJXIiZL7ci&1Tg&QDU~+OdEQwamW_loO-PcZDG#Xy*AKYrjbr!9LFXEcAdR9Dv?qu z(GIb0F?LT%YK2=A&-~sMqNIl&*uh6QE|5t6e7uyaG`hbDZE4tAAthY$t)=7eIn#vFAI^>jt`*fOWZhACls6m~HzM$T z4M9kZSm_;-%Vb04wmuGY$#^BPK{(QvYtLTUGTs?gbn^)`$M{$mCB``k)FWDepO2lZ$`4eIZoI!@i~UdQg-y zr}Yh?9$6QhXi>~Fu-|qG5B2_%zs}U(h1$6k=FhpL^Y)n1*2W5(?E&3<`Q&zt%cVD2 zU)cnVMMZsmT1HOv8=CFyON!Hh#7}g)$+c5k^%i?sd45HopNJCL7ZvHmmL*~U{ibkv zOUrH?gBVtzZ#$ffjx^~dU;9KWHXQ7Z=DDyP3*KPAz~t+FBrzX+6Oj9H`z0%l z*i~KiA#Wvr*?KmEW+*3y!^TuJsiQ)6$RyjjCq!Rll51eiu#r-58Y3BIETM$Bont;< z|8{7)ZW3%C;udkj6t2%O*lE1|)9Xc@#H6iO2YvO30JH8|#{ACbA6FR(FB;s#N zxbak%{7w`S8KPJ_9|g%7;rTw9#kM|Z4OeriuCSvW+#QqXvz}1=DX{yj(?z9^x-rx8 zE-&lM9@|f8%^_a)*y7O@3`z;+$0@!EJ0)qe1d19jR#IV+O+ai!Pq{!@c=w*_(em_#ELBoOE_{~G07I#lB1E`q0=>)vXusn6|p zldZ&`%8Jh$u90JOYy%ZMQ5aevE^IGV%>j|K+_0u&;+!ieg%4~kx<4S@@m4@Bd6@lmbhTh$T7M>Yq@lT?lzZpMuVsH<@c->?DZlRWNO0b@IUlF7vMR-w zWfd}kim8`DA98h=2dA7*z}oNc#|v^S7RAO=Mf8T8&ROt%tN2td;C}dD6^MWS8@|kB zBdo#7h6)N5TT01KOp!OgP6In|7y!6`E+W=Bz$&}6rroc}k($AcSK_rTQzQ-^@0wV* z1FU&|KoD{lsJ#9xZr1OIf?srLJY&BW-7l&sd_VYzy0UuxHWp@%T4w`wxUyjw3_`2aXgDC|M##6GdU6aXI$lvmY z@F%iG08zn)kq>30{Gxc18g%;V38TJ=Ys(b(l74SX=r}JFu5&^ODnX{Y+n1w=Ysh#` zUN$WECG8PXBfffZTe)?;*89xvX>g<@1|(^qCQTwQ$u9XbHdtbf0OVRL3>I?B)Bf5v zTvDIOuHHr!8m=N@mlx8>=7I2-J1a<%^Q@AXN3!A@pQ4=%pi9|!~ z-@1URe#b78qNmH^6N@39j90p%ITP+xX!2kPm0_er-1RAJxSwoC%M>(7#8a1BLuX_#0Py*TS#NuCWag8P#>9!zF&)DaZ->N;M> zd>Ia(5)Bmh@i|H9uE&ZMTe{C{={{NuJp(MmIq#>!#*E#JL+F9#FYlHVz2tOzr@Zgo ziks%^GgMVzb#(LEZzKfH1+emqbv392ZaF8!wAtoVEUk3pvLUfpH=PW0zh zaHKLfd_i3iVt#BsuR_TTz1JniSOZ(nsj^{#9$JbOpWXF+eE8 z;Ui`bg_F*gAF70XIlsKJdHn6bc3;@w=>>mo@7ckO>FVWY4wd?2zz33%E}Z z2r5X|{)?P#knrL?|C80~zr(<0K)17ZOg+?luQvVCt@hUM#pJ0*N`{u#uMbYz{HIOi zkx~eWD5z1vm0sCBfuyEUDRqppB`3vnsb55K(iN@tr1n#zTr()hybwvoIe%guJ^%R6 zDB9@ism_gd$TC{$KPNZSGVGFAJsB4t2L*s03;h)MRe_`8jB zYw?Ax%Nx?C1wjV}K`K{&T@HRXy7qHfjo0q$pI&wag|`~{-FJ>H)|G&WVcT2Nm*ED> zu$vt2qbr`PMMzX>#n@7CTah=s57uv(d{?oKwo<{+^d~pkV;l1x2Sk6_E%_$EQ!&RT znCn>_gF&p>A=5j93*Jqc;#+M>>SiCN!EnQks`;0?!();m*(?OoH@LT`4q~<_|J0&J zj}mQJuw)?^Y{^-q<(fz_QoCvvLS;k7kafNtj&)q4ZQMdOU?#cPy*2p9FCL9&dUmx%t@~y_c}qqcHgHl{Pk>Q zROKYt7R}`*FJ!KZMNN^`lI^I$v~p^)M0qqhjvR-haLV%Tm!5qy3`f;l`jLO>x4tXY zETe}SCQLzNdrrVEq53;=tnHOP!q_l7v`V&WCu%e|<;$oc10p)#^Vl<5+|Ee2n0R*Ke zsl-82&;&eP@>1=o@%Bz!Hi{sPpjXw-19gV)IaD8;+Zk(AN<>fW%_fZXcfKS0fgan7 z(ob&+h1p$iaS-^|HKT|^hGYQNDy#ZgyW?VQME{C;1vUBJXPq0L5B$xA5N(_$!ay3d z=KG}t+uihA*HSboInm4THy8|N=MO%v$&*>{!$ z!-Up?Or`EOYKHIPbB+J3bTVl7Z=56VzmbrJzw3YF_dlSce?3f7)SBa8kX9yuCS#yX z9qirVK9)k(ZYN{bI_Kp-f=*W$)r2Y688pfy))d+oESW5ne$+krlHzl($R->HH%Zn;8U%icnq_f{OyFp-2}=R}JRyHrK;L%$JZ~XdA*ZHa6&_ zB{F_U7%*!5EG`UH)fS>n_}-vdFT{SgK)YVWGZ!sWx0ocF)>$!~UeR>4l+|u&YIMWib&->Vc{? zOXd&>2QkFGrlADD+Xa3^eBjy?v`X>!+F~tt-`e8J%}RBp_5y(f8ZWFL4!w5YR-?+3W&NO{ zqTAfq&V6kN06&?4YDsK)4(mNpr35$Vd>EzxW%^`nf*dN=3tyrxKCHa!I3+qwe*x1aZTx@;ZC(Rcshi69ztAR1%b49%A1lJn?+3G(ok@X=PZZ z5$UCvf+r>brF>YID`jz>pRy++L`fY(Wf08HFSJ+i-#a6ep6+@jre;0Om|uXJf`KZh zuHv;OXHm5n0EtPe!{O|L-SQhOmrUU`bN-Q>ri{E~OIs;&In7|Y-cfOj4p^NweClC- zXL8S%dT4U*`-WF@+HNR%VZxS3CB5X;rKraHlh=LJH-O&+(&81<^?vXxuD(FQER6{K z7p7nQQ|t?Y?-qg%6<4pU@~t<|TW{2le-kh{Fgk#~nz?BvCHk9y$;&BcH?tCOYb_tJEBZPG@XxyFsCXY;&DwF7Y}Rl|LfWv7Z>fR$O|>&2Mgu+J z$$i7JP&qu|ma>%t8`kANGzssjYR9>G9YoC1?P`>WqZlygYyOy}vH4l`p-+SL>##(` zt4HMc=+4_0?J<~*sm0Ujof*A;BpR(OF(nbNX|BGhE%tU~sI&eLpQ9h*4)5;&dUPN^ zVJX~9PyxSMZeV7vCQ=RE;D$zgeZJ)@Z_IX{afZXdTa9fM)cL3%@^TxmZ`H7v%%yM*ApFqt}H8UUWIZ{_H ze)#%y>)de_)5WHO6IUj2&hlO61v%kH=bt;$&o|xT9e7hf4kQ>UYPUMy79{gS(1b|C z`)N1xW(;kYLbfmG;*vt%)n5EF(igM*LHi=jd}Td^?rZA!f=i`+?mXTa=w@ow|ITuK{NfOhPuwRgp3Q>mE z{vt$NMgqq&FDF$mQN?R{6u>d8OAg+93COg^r7cz;^qLO?*ZthW$RB&ZyNu0H;u~4; zczk!Cdr>(#gNA_Vz{uHg3!nqa>#RVJlc5yag`G+3oBBkBn#8@?|{JI}d?G;~~uRq1-(6Zr--oxcrN97l+1eqb_l`$0+s_q~A?p#o;ut z*ycmHoxxBl%0V`$7S3JE%I-<=kekJ2g+dVu`TFW`So?QsKB4jMNuzU_9wD70%RG`( zFT(Wv95uv#4z-u=IA@O`fYdmKsh-j3n~&0)o!+Z=>$hyR!mYD0aI!~5bROA7(OZ!`(IE};_6I+u?mWLn!ShAa74 zX^5v358QqfR?*X(Tb3^df?oWvd38xqw%IiTG*q!1QTzje{7YZ|m^CIjsBX(zO^-Hd zarQ%2=L{A(+Z9;c)X$R?4MLe3Tep-aYGQx{!tJj@^aHZWgviRwE1xm5C9+-)l8kQH z9qV?}43-K0w17u|WZZ>DeBq1Ze5Wg*3D4B9YfGak-m;qrq_~v_n+Hu;Z6{`5jvF#0 zyi%T-0@5$^9$Qgl4#GowJN0`iUWMbIE*swyjsHEn;hR7^;?PFi=kT_|Z4Ts{Kx>iX zteu?(4;^RqSi{OI!Y2iaq+CTi(?Tb{w+Y#oqC1HIrRZtd9V*f__a_`6MGG&Ra3@_O z)#!m7?VKvjYh*I*!#IJ6hhczf{FoPjZ~G%iff5S}jNg3LtnrCX3Jzy>_0w}t(`vl0ArnqT5!;#0;{k7JfIB&R;0mOQsr&2qGOoYI@@+AUd`HCoA!KxF^>a|#AX>^h2u$FT8uj=c@ zwr7;Ej_gjwWP3`IF+w2S-B0uM@fhy$7o)6=%t%mhKHG6O&QUiQ7taKh-}Z{281!^P#`!^Fy>i>~i{D&!vf z`tC3AUgJV4IuQtn~O>(>|NJK1DTZ>Q+fcLKK^}zW4)i*tFUC!KkPYA!| zbmeF%QE`5__RWEwgp&y-Yby*hZPq^j<>#YImu-9(E|(iBj+a{wE&Ng~pm{wh2tr;^ zgpvSZSNXg8M#j-WxpTaanR0!{7iS+oGGul4I`U@Z`UH;GBg@L_VAHstk5Gz(6T8M~ zPU4wz4x>G_{bL$~Y7R>xm9GV2WpfulRS0GL>j! zmwNx_EDQhqPyUxA%nC*2eT3QWUbCuqu;-{GUa;Y-y)Ihr(S1(^DsM}=Rv}YTNNEj> zgy6B|Lyo%3ZWfkHXb~z-xe_KbgT{llYMzpuZTlvGm44NMB4?lZE2K-{yF2{C0@l@z zeLmuMN}1iIl!HP^+7yUP9eBE85V5_yMPyXTf|Odx46-oVKRVM~pM|q_A>$iM z6jCkk!pE(h=8Tv2982Tp{Kv2FzZxsOeNQkmzC#rVDTuR$dD@u+Ft`ELUGYRxiJfX8?n_v&)kDF+skqZ@ls ztUSsMywd&!0Hr?oqbG+6{CMXtRrqg$kw<>jzU$q^i01>2v?lI!pD0yPN@p-D`hpS@ z4XoD&4Yxo3If6@b3^=su4Dh=Eo`0KN?SSU$k8SPrkP8_=*n0*T`2^)Pc29eMv>~wE zGH%>0wBXqrWwqxQ_ClPfA#K}aZe(*E9mKhs$H6Z_8`Q5` zG&#FXSq+)({m@(oDr2EMu0Jmnf|UXbs#s(%YS{x*$V0lZN2x<+!{b#O^5X0?u7eQz ztQ8ElP>6mr1*^-d^|k{JWY)a0wjM_qI<2luCRsn|;o83N`@V(lbo~B)vYi}Muq>;i zwWG^Zl=Whf%pH4IF@DL^KO-^b^I7R7mj-Bscmph1cX}OIFM9bLvQEy$^mQcqI!yrh zQs8K_-K8!}&r4}mK(K>!_n^+*RR2EuNgCa>y}EX`t>Nta|FHhV|w^Im(qH9)Wz%J z@1gzuU}8wAdHM1>H*mSvaGOL7WVk$qyQFJpLtIf6Be2O%A^?IGhmMAjhczb(R`rWK zG?ovi-}|LZMD^8Tm)wkQF6ZsZ1~vhLt~AcV?d!?d53~o|z=`~t;G9lHhkmP7HM9P4 z^)?zCAJRDi_Gm;F29Xq(#I}rQGDO}rRA~yvB|MRw|2Q?}u=eGJB0b0lnPYU%2xQoh z&S2;%(2^BaH{PTS&0snjr2M2I2!%#mmX(n+)0v<+vDQ@ND)z)ZWO%SLBfEOF1oMUh zq-EwUE|%ZV{N2F)6U~Oq@_KIicGry|VqT6uP6#Is+XTCr8>S87-k+*4*w18JjDPl7 zkaDK?P_XkY#CD91y|zQ+(>#{sbL*-Q+r&T z$qoSo<_h0njzJw5iR4*!XwwK;ojp(7hA}e88D#x@>%sfO@|ShV)JfD1dUdJj+&)C%yW~hj2ZcOr4rcSu= z;WBoVDE2CQw_97%31n(}o(13x zrFSI^8CKywcEMHaz+z(MTzNmueI>?{o^T+CtLr%=mLb@?N>y)(iEuN&WeTh@ta$`+ z34*+3+iSx`DWRtmw=}piqw}mWx!l3(>#tUYA>OFC>lv7yc`J1UKk$asaK;n2pbS9> zVxMbwSC587mecLRMq?-?BNGiCOc%=zCxD%o_cf+z{XQ5g6LA%0fu_L>35t0enP)c3%A`B6^A6|m8g1uMrb^GZ5mBw~ChUQCr4 zUDbNaOUfV7wm7*p9@=IA@06MfuSC8>{(bNEAGb9Yejx_rH`@o?CAsfqkyL9YjHv{^RX5RcFDc!#4T4)%{u zH%aa7sr`brmgrvT>SqOqn-&*Qh}lG7LTO~*e)E|zLIDd{sB zc=NGk8BQaF{og>_{AgYpK)EOq2{o&xhVXixJG3~7>{^GtkPuiXDI}G<(n?D+LRu!4 zL^KqW6EYdBqJfx0RUFCuk$8i-+`UZ zyPcEk8)7E2jV*x#jInsto1sFJN^9jCceL*I8r_BZ2aTDUgG})g3mPJdY~i-L74@P* zQk{9X)nFG?S*`vwV68prO;Qr+0(rZ6*85wCqB4(5TAP-$p zbg)HryOV&{kzVt=aN7qjp@ZhPmU7$00X z1a_twVj+mPE#qA|W?*$!-J7{Li(+&;ohNJ`Y5R{4(-GFgghtB0yq3pntb$L1!a(eQi5nZ^G&J(VCNHS`KOhq>7;PiU9IlHlQJ{evi@~aobuy zD5<3D9n83|bTXmG+j%l69A;6f`KC>@>Jj9zXRPOu-x=pMY%Zn^)K_{uS}bp|G23$# z3EK|4_G(kz1Peex3GZ^>x7b&0G^%~rv%lpia(g~R;DUF|5NRbGyR)2rq59)mMTa6> zVsRJGZsy|3>bfeaaGrsIW96V{6VyrUvu9>ONi!UHah?Kzt2I1xm<^O7Lf+=Nt8tj; z*2xT(%hI3kFV#weNqM0zdwF>@Uis=fu76Vv{UrXy}E0#y0_+ z@nu}2+f*X`^zlbGjIObu2-`<5SEiCGxe6FFtKwl6_&ZCB?pBn{gO>Fpo8)>x*|x!l zJQOJ&%1rn9z*uoY_~=hqG^jN7mZ5LlJRd1fLJ9Tr9q5?&Lb2TDiCJX!MOh8I26kw6fxv+FtRqDGsPtJJ_ceos8#;3Z@d&B@Bs#fTTYr|uwYOJa}hC3Ln zm<)}YNEMQtx0}6-qOnyR_WZ{w@zem-xn-D)RD;i0+JxdtCixVVN#-^z`_xrFU}TtiQRV1$+7HlLSDBnw@+Fd21H z$)#g@nO>giq>)F@ljI~=RpNmcXWUYT_>4*GQ^{5#rIHIKW#mbT)3x#rgo%)(BEFFt zUQEF-XOwDAvufm9O@naizCdXgW-=U3d0wkz@Bbk!;o(b31jA0E)|}u?wDVqal)z?lv@O~N)#1>MWwK%q%+SI?Sfh8;#*qNhUGa{bVWN>T&U4B#_b6?rg zNwcC`O5*`$41X3@je0HVQTm2M$%vKNE-WeBf6F}NQ)vkqeGy~k zu%Tzk`zBzp#WL91o;G`DPIQ)YmY+G)gHwWs_`{>&Q6i0AYh=@dMy;kR6G`zn<*h)? zctq@%#Qff!M-NA1GD`;cL$IFc-?Y~a%M+77>8&C3*8i~C1Ic7Ey?ZDwRyl^l>3-qG zjc6sqQ!?y}vbLJ1!!Z>dBcv5rCs09{aaamj$uWez=aJqcsS#IW@S48mmAWKNe)!+t zSQ=imzp*sj|6pkZ!NLMWUwH>M?5LcGnQDpdVl)+gth1HS)ttMm)2{Ac##Wuq+OtC-T10^G6-SFk z{diGfksHp#1-E(Y(q&t_MuS`Fm@0s|&`sf$cNR@hp`tF;>3X+FQbTt zVg>upCD=nrTpJn0i>vG0c%6E4X+=BW#)4oyfjVPUHGomaWL?<4=(q|IzH-X%$dR1# z|JYak|MvffmBAlh{`X*I{9v-Oe1C$`E^FFN=rA7iRddwL{z2>Qrvw}OMn}GXJgXG{ ze9CpGhU;jO$@lQ^a&FXL8NCD^5aY(Gg{z#pls9K_x?K`?Y|O<@jvb*js0%KDARF=k z@C@r8*OD(ZbUsgp-#XDC8Ce|DokPv*z`wLTS)-YlN-LRj{c|Y;^GV6VATVp;( z?-;G4`uN}(}e?_qucWM`4f!a^{6K=W7^&ecT9$|dVr8IdTDg@PlA-%o*w09vq z!<N)WG{KFxbXyGg8-XvKlbz8zRPjL9@vd>`bn-JOT$me*jyw ztmx^LC~hU@jP+E}h(nzFtD+REUcMy;E9KLh+^~Z1bBTB)v4WX;`|#}D3eW4-$gW>8 zJ4toS|Bt=*4r?mk_x+ioGwR?BA|Op?sM4i3!EvMt0Yef(2{3dBgdRYOjwlL2!T=^h zC`yo!LV}begyt3mq(vcw5(r8sp(s^E(O+iYz0YsobDrOR<~+}R?(gh-PyS$KEmpp3 zt?&A-^=s@Y~uW^h}azKmBXsY$-J9+HGhV7&3yu};!+r*%L?gm-y)Q&HqHpJ{S z#1N{A0NMuf#QRbQdF8JzuQ(4eyoA_iT`9dMDuFO!afjab#mnwl{gt>fja$f))LA02?rbAB}5;p+H;Glj|9o_p$rntBs@-fX_p zqBCkgG-oD8#JRH$2E#?vd=2P=WsNqjGwhseFcICR5ms46s?9vtm+d90 zZ4xz=L^5XNZj8;h!Q%CZ`<~(H<*R)Q5v|Qw#zs`y&hU0}*$XS>8^D-FO?8=;Sv(R` zc7yNzsHXWv1ay%Ko7!}{^i)njz}htj+t}Ew#9KV+6aL5$5OtE$i&wGO+Y64~AlryE z=g9<^+B!Fpk^r^EAzARwtzp?v)hG`^GVB{}|$qcila{?vt{7`Z@b63lNxA zT**X2SsKtZ%^wMkH1J6@XLzkl->+HL=g$a6=-RpU#-66K5)#X#jiLT6rHbF_(i2F` z_iBUsQ3t7jNxFe>*w^z^V^(k|R`lMqTryy3r< zuR}SP;NIBB8;3J?#O4c=f5wn z%7Z-?hrRkQO|Qp{e_$PR$IiJCf3LU0@99Bc>Ov@5B8F5tXCiKBA3ScCIa&Y}w&?6{ zrBr$ywUF0wb?Elmew8vKC8^uDAaQQ`D4BS}X7>f5jn9w>tDJyzp!0xk1Ml@t7U$AI znv{GQJ(2M4W+uet+wAhAw{<5I8LdJ&Z>w6U;0|vGNheRN*K6u?^EJupzumRz2Hni> zE4B}mRn^AnIYa=?RIeBj9x=n#{C_FAe`=k(3EFzo5#TXRZW2N|C3n(dZ$9Ze2JzNR zP|M>K_zCDy)l97i(8Q|i79(&xVaa^I%I_Pd*5JZ8p1-sVFyf@5Blou;~I-kSM_T#2=56nUyE zN1bDBtiP$D_U)aWp3*(@V5Cy&73mMnR_Wh0TYrbK93vZH9(a3d>d1TuHlCie2)GO< z4z2l}sLt*kpYOnL*!~jsMTTsJ$&ETwZJc!7{woLlxMr6v5US7py%Ex71+ET`i?NB4 zfcO;BU#z~mH>kt`DB2z;HnyaSyjd@%HS`s|E!;tLkaa;2xmU`{0x!~NY@nU>;h_*dY=gX)*-yC2JPTS=j z9_Ewh4xHUalri$?s#H02T&Pu=DL4Z0{p0bum(Ab1ybE(~QYiP>!K0Ly`s zW2i!=b>L^N%mPZ*u5CVh?90fvJzgHLmz1N^7G4X9oZDlXLjp3BKPGch@30AWFTIU8 zsRoTYOaQNf`OD5LoK62C@fx%fYajd~Nosb%w^i*2zk4<@Jw_!mBPA~6;GPJmJNTt? zfrF6`&fXr8qu@P!<|$Xt){Z9govm$$rtWwBQA{@;@kczq`gTFVVU<`Ar~5A21n)05 zTK%<+%QMR`ma&$hL96X=UmQW$C>aBSM%T=dWCB1iQ-yWloFPX@f%bq$t~EpyGq)P^y22LMbZqds09qI9^2pO?( z8EQb78y^y$UH4+MvQZw3AAP7fnYMBC7=c2W<46`u*uA|z_TK7MLi##^(R0bFuSbi! zkziL-09r!N;Ywr5(86xRO#K6jjj${*K3+q|^jIdW{+a>w*#bbM&TN~K6vUNn=v%z9 z-~6O!oA(mx2gv3)&-n(ZduC_9k%(~Kuuf~JRpRYq9)N)xNF|f3@RVN5?!u}ZK#Uke_Eza1CW||LGEcSgmEMj~Ct!lp?KbLa6vhqM53dlV@1u{ge zU2SW>(t-#K)14^KF4z!f;8#4B>Ty_X8EWnQgL@&n79|hfE5(BKeeaI1X}hnqzf9~g zl44#SS7^8d?LL-ve5NUTd{tNSjEtF!Ib_uk8&VR9fD3%CG&x_Nuly(g^OzdP+&L!c zNgU}iJ<^G}*g&P5r4<2mpY}2TB;SQO^fO=ekmKI~@5m|@6SivJG2fOmYqt~6v#mVp zd@rZt)YK-Zty_kVYq!wwWL*;akDG@Km%F<;g5YIpBUWq$uC3RU#}u~!jk zM*}*MNg#JNr0XMSp@yE0k%Wv8r6uHAHZ?B&GRjM?ETR-&Zmlvf23g!+|9~zis{S&I zP+Au@Y?QSbC-xoxw}1Lt6whk1?eRyOEYzFr_EwgJd2yK#(1;1SyfQwWxNUC?9PA60 zqQ%Ad9?P7&t7~u5mpqtyY-Sdc34_}3{QF0?$J0Z^Jx&d!z;m3>y;D!W)eN92HK>ZzDOoynoMv*@et5q`4u)Kf% z^qnuuOR5PYoW`Ga{--hde`nhTH;Hy%`}y$Qzx>ln`~UNwe=fZI&!3^}H-%3GpT!z3 zOj9g0SD1&%`8} zZA5LVhio#>8+bTaK@4YtDTxmri?VT;`fL~_yT?Wt8-fhY>n z6eCAX#mFRe*uGFI&D{WewWBotyEuG{o5G6Yh^v0+@))Q~gql0;qKbOV!}Y702@+tL4#3sWn?W zOQj_X=)wP8z zD7nP=kgE-)jWbk9oJld4TP3qj6S*)=I=NiC%*vcfj!N+Iea@GND`@}bFTBMmV%Z_% zmJ+yfRU{2ww{GkD#VImn?4CZB5&Y>r!8lh+I6>95sJ}{9B0PF{`SzszmzrBq0d^$G z2^RYJbZS}WQL<7|`y;N&u&0|3WvVdphw!jf;|8yJ5tLcUtcV8HUJY1N*IG;T6IU3&BxV61q;P5+Gd&BBPq*|b}(ng}hIhGIx)!atzJ6RKH)s0*L5HZ&F3Y8t` zZrxmN(p4UjDn9{O5P>YI9l^;GVJ_i8Yvy!0t(^@loMJoa3R}#CYdmb_FjPsoPhB7 z(I@vCmK|PxDg7&4=q0%Aw-^`wHa!z)#+n@A^p!)pf^&TAy-}i0=eOlB}#Qq0f9uFu4p>1gjpn zXg~7cVwK!4n878x*I(d&`!8ER{7F~LKYjlHx0+2>p~)@_I@$us5sk>i2`=lU$6Q2={&*6`2BrtfS9nzn%lWHe?XQ3DbBZN} zN7F|n;$xc4<;nmUa?}1fVqDT>;ug5PvTfE(s{fd;4?s)NrC|NZFAlHxPPfL)-_Jvy z)dyac&vZ6@>a4VIE#BUsipRxOx#=#~Pp<^nXD+g;F!h>q=F`(62EYDz(ii6ba*tBi zTCS2W0H@!+X4w5#kJp3eAEo|n{U4H&4ZLZzng92sJO7@f#9xBZ-~IO?{zWn>>VMq+Kr%c# z%9v@fpJ@*PyE~7OkxB;kb|bFE5?MY+ttc5#!yN#P+?U*7$-Qs`c&&c>ncaY}xWos> z!+a2lOtoTI;~La@s+7tO@&qiXP&_j7m0jIkS8<}wxt*(n^la^eA{nUz4`CTkF!(yepoK#hVYLF8TF0WOh3Sj8MP4s+c8`6cS>^T@&Q{zZ2qt>=suyg2;LekM3OgqXk zgj89P_IAjjPOEYWmPIUnyBvW4nj0OeO4eH6k1P#$r%__ov3X zZox$g3>3OQ`Tn4+_@cN5Jnzb&!aD}1PD>?QLQbIHL%CCVJ0)Av0&c20yKp%#4rKh8 ztK#|A&HfF&KM!xD6YS@l>~(ux)KmgEclnhm8qB>U33Lu4quMORzLfsc)!YB>9seAA z-(y?U4B=&RxsW>Vu>46X7MAR^Lyp<+J-&gsFb9#Tcu*>HVYq$h)5x2>0nkySVN2cg zS>E+-vfiZ&W^n$Yi2TVK?#bv>kvckewp|bH_B6rQEa?pvo+Fjkh&csiqRiV|;5(X% zUoO$3hs6Kpj3%KU{x}hVZEoobO1NhWJJ)NZ{Cso7ZWH zc(bo$i)WQR{reZ$#?mFt-c;tXh=(;znkHD?SoD$t%?=+-CgxyVp2&8Q6ApbAvu(@1 z%5ZY%p~>of)4i~v%p*^IoalX;HVSQXObG507lMd>&A4X`g3o>X2zt8N}Q&jc2^QRs^yPI1OAWzd{M-*T-LFw;ka3U7K`@Uy&`f5w81%W zSxctv%7x~9mnRbH0g2tnveEKhz^^TyLW<8oC-^=8c3ZNsS>JYF?bjl8Ey27@A27Rm z@)-K@MCO>wO3v-FhXitKze~1ugyUC_Qt@D0do3>%DRnhZaGKO;AK5|^ z!@qrXHePP0J306~>{?tDwMzP!ya|HLj}i!~qaqx0*W>X|X6G!&yJOMQ__mYLIJ>&s z_~`{ZI@Os>-ckBVIZPWPZ^XTqn>j3Kp|}O}b1hU*#eh@H5MWP{M0T_2a=@N)ya1~!K&E^PAOb5 zqtwuO*91~Jqmv*f6R!mVF?Yyn?L`Rad@|w6x3B$nA4FBwa_^?b( z5&qzQG9XWrT&jO>h3|GWCsdBy#?`s$!dF3BtoKc4ZM(O8+d@W_C4*te4W$(?$ji89 zY_bqr{PH;u`=rDJLe8@fm2X;MC;$3@I6nfj~C=CC|3X+&Ihi z{>Iox(YBckhc~HKk+LW2FBwYZ1?*vDP#PUS{PA-7>il$L`EL{Agh5BrDi``;Apy2y zZ)#ced-c~~8+&8@QhXv!Mziz>;ux zOvxYZs-z%{sMK6`ZeR9`o;xac=f+-Zm3>+o{A&!Y$KNAw{-*vPLj&#b=uhhJk22qRU1#Ul3my^MFQ(jg zbkgCKtKOFDp&Inc%b45MT6WIqL35q64hQPtN<{HW|z%DEyb9r&}@D)p0h zpT9&s=Z#a^@d&lRa{jJzKBV1fL$(KFbOH=BTI)83z64xe?4I9inSB0c#@_VhuMK9A zAHw9WdkPMRl|^X&Jf437)yB4~1ezI3^*^P-HSwBn-QW5>nA}bW2lxjh&>>hjosih- z!r4fxts*{q+Vhu{Wdi)J<+EoICPj?{_L%m&r#(zk?SXzY?JI6?;@Q+}p!a^Mm6Xqp zlIDS(D`mbmG2L#5@jjYVktE%3U_}G(%b{(z3ll#?70&NCS|NQ>^>Cyi%1;^R)lKgS z`F-n3yR0dQWCjwya$~tQQrqEK&u%y&+R*S!RA|mQVY=1u+-vq3(a;Mi8|G|d<V}+m6y22Uz;Z`N_Y;$3wo~4?JS{V$9iP7Ed1bO zE4Rl%z~PtH?e+BjFDHNPqf~TU)1l;zR01b9m|8sB`-6&$D^UX`Eo5W0iHgdarnXUG zZ*?FMLmKQJty_ni-Z=yqnHz0m>7*JJhwnaZ6nJHkUmTI(!xYF!2GXaZ*7AzLT~R+d zV|S@v)fp4c5%!Km&!n&JH~xE zWlN^F(>#^aEA&ko2bxSkB;67pUKy2FPQq+>E8|42&;fy`#&p*uBqY%?$mFHjpYri7 zA|vtB5oJ*vZSh7%X!i$0%Bhi{@OyR?NV1h#aLQ)R#9y|o?{K*(@}MDKBCo*7WnntI z|Mv4PXLHInzV>C4Z|B)5&rPwvj4<^DK24}%+9D%P@RM&QdiclVXer8tIyUxgGI=-mT6N9jk%vwVI5_s7~ABFq3jP9o=$Gi4s1U&NGs#$81HNDjrQP1Gho&FLdQkEvZ^* zDpx(S?NhQ_($sUShWmpw(0HH3vdYG&-1dn^ggEbVNvE_1S(g^Fig0JK;KoqRtffSC zKPlL+9w_`|jodLeZ@o$7N{-2(=V{avkh4Ioz<$e0SvL-^QtZ;oB*m39b$?q{r`u4) z6p+P%fM{xUM#y%=f*fnTIJS!xM~($|d<3)CMG;q*MAE&S*05&FsFb5 z5JW+58t+p%H^yzOo*!Ku>DHwZ=?EhVpiga52r>N#|G+M%8^GC+wVgU{ZDd0W{6!N^N!(8S9X|7ZCRVaifw?#jW z@#n~7=|!d0gL~aeejn1LPp-RH-aiiUnF>5D^WCqxm5|zWmcL@%C!Ac#LEVBGV=W4A zL9G&oE2TKF%aoH!xc6XsZ}AvqkBM7veiXx8!etL4f{v%)jaA^}T6cq(->{JjL1XU@ zBIA}yyaOd#lS9kyU)Z=rXUZr=-U-R|%YBIlyS)0?diAqdzKEE%uMYEA(~qFoc1KX@ zr5rd>p{TgY`Asqa%r>hISUOt#6v$iWcs6=hNrnj&FNQL02ZljU>7uVK`9q`B0x!fjw_DNyExn1+RPln zcx(f!EZSnBbREeMNW>fLE+u8S`0A^9r7{!5sO(esRB=E(_S0UTvnXOoQ|$=6_-ZnH0Ih@mQRaD zrh>#ji>-u1OEa$_xUV0$pR9T9oLb*zV(;#R);XR&YD8asRF8{aof zU%|Fr-n}yI(GN&{JKEcJP^-C@51p+YXtj=4f`%7qonhO2LNDw+&+XIWmLE37g>WsD zxLwN_XKCxL&tg6YV>{@-E$_Z8{cl#x{}(6a{}`@L$H9+lV^LhCmO91LlAlzhY`(85 ze%t%Hjgt9G0I@7zU%%z9nCF#0n)^4n+0jX{lfD9FD6C?6SYIkB#F<5WVu065y=FRE z_{`-YF4;4Bk?8FwN%4l+c2;h#3o%ro<1@*t)Lh`$db*@9}BKdF-=RNN?6ODZ<-t|@7 zb;~P9IrX7QUB7-IIDbX#5TT14rre-P-muwkw!2W88vrw>bj6OAk=xqHi7h1j1?AEG zmG>P*-`2mcieD#Wea!P-sa+EHOd0~VTxFzm#Y4}cp)@ z@j5~^PBXA}M{A2N;T(#IBiBla<=8})46Og1NA*=pP&-sGSt@fZ7<>=;n6{;s8$+6N zEt8?kWZeIdcXlv7jzN{1!dFkj={qOi8W*sn^vCuw?|E4S!>_$m!-{k7QZSHYq8R4q zf4kBDYAqx2w;lW0p7N4c*I`QX$8<5wN@b)srz0ujMJ4?**CdB3akW82NqmLZhDhyj zOwRrk^^v3JwU;l+%=_B4bW;=;R~5@s?44Xk>X1#ZM+_;wb=`b0m-NxnI`#M~6ZC=W z^>iHC)(O%k98-qg;-~@H?}yCr=@Gnun`sJ`c5r4~V8C=7=&_i^)UTF_6kEhP{a)g} zXN2Y>n8{f$8sz<_p!fT~Gsaevt^h&Kd+w+OkV`SCf;qx~nC^*>>^-(x|#;rPQP{&!R z8~`3P6q##7r8voOG0m4E$g3|Nuj%l<_fe2@pJ|G{vdA~qrf-)_Rv%3ik*KzJUv9Tk znqfLvfPF+OOlJkv=XLb?VnJCPX@*WFPoE|A(9a$E{ojhvKmT$#d$dZ+-7;~%yV{LI zhmOV~UFaHe%ihid7cGBHB&Yon$dKB0g4O6&UzZnux>fU3*4o}D zB!60@SJhcvas1_b@H^39<2Q(26~M|}E7^^ur)8#?;`jHDPFL;m%fj`W)q`G(-TLjX zsTBn!z>Uw9N{Ih#uJ}itj)2(V$utQwGEr^^SF}0EJSYkdq3l(jz^cKiYFXjDq$B(JW|8zJFx~KG9y)X~=+@OJ=3uV>^OlzJ4es z_BEsy$cv^+z4eZ|mvm%pi)tK{fcXwHGF%OdQ-(m$pT$&T9$x-U>`SKF%LKX=O%Q*r zs?i=HVtJ+k;>RQlX7>k5H>nS>cfA+b>Wluc@i-xB>^L&V4 z4;EY_>VGm*tV}WS|35>D3lJQfi5&s9dj+A*yjogU*5Z2Y466yK;Ow-qLe zgIrNzl#vQAg&h@5IYoh0!d;!)TIZUS4$c-QjUYIWx0qq^BU3Fe%Q^C!#1__L4+J79 zW62Sud@aEy0=nnxu52888BS4C7};8f^esU8e*4~WUZe}OvFW?tZ#{4Ved9jD$9sB* zIu)4Ni)+rqneB4~zO~Xv5cfK`oUtP{G2TMs2F(lfx!SmBhWN9$4^-7n=q$B}d1hiTR8 z$RUvU6Yp6Ys2)1G@Mb%9WB*$nXv0c5Hj^H;xGr7%q)8+uW&garI{QHCTJWb6^*+0P zK+1`Bc)F!LIowC2$;zl83T@##H7(Oe)g)^(X3S&&^hx3Jd^48~#a-mdK_tD}S(fq3LZbMM}?<4U7E63;-5c$n;UtTPq)O&3Y>HQFR2VY+JuC*A<#DC)60$ zOiG|f7}fsetIXx!#Lm6Z^rqh)Y&bNgxI``~uykz{Pm_>NB!nnJF@P2JX&>jlHl&3H)v_8v;ctEY^9IibjEpXY9wPTWZF?SGGux*3E8cK58K+t5Fg~0~1;CJ36^< z%G|$RoX$})mymYe2|ky3A0RlkWAzTBQZT-*-;)eaGGmYG#6O3OI||xgREeN-}g-yx`71;XIGGicHdMfx7w|_ zA~?GFTEZ|U#MCRFY&G;o@AfEJlVFiA1v;_H4BeGAzsw_VxAz$z$GpV2A6-E}AhZ1Q zK+)4_oA@CJsvYT`e72VV3Iev7DNs@hSY)OYhEKZGuh#zj${0Be!DVp2vDgKX%{R^2 zcJ}hgJFN8x(0K!b*-b>M4M(ckx7KHM7saWjO9g>7j0wy8%@lBb^=tvzIFm@E+-42L zRmQMZZSPLHsw$dWchiy8Av~|K;1NjvMI#MY$-v9^FIuWOlu|l7rohfK1|&?kW5o5d zI<7S<+Y8lz$T7D`8U_MP3PAuOum$J|=bXI-M~GKJ@4RvG^a>0qm6a1_4o{q@K_)hF zO>}Sq%V1%Sv-7?C@^s>kCjtM`N)%x zb!-8OmcrNWb!!2d6y9?#+xMBT8Q5(%WAi!g$vxlU#Mo3$Bd#RG2xL!I^U{J?mG2h} zDhA-SW5DJc#v*IGJ65a+phCO!IY#;!My~hqQ9)vQtr5L>&?lbh6vu#RzmwrUHL#m^ z6ulQr%xi66OGH6j0f!l}NFQ(9o%BI;$X}z~Ce&Z{O6uc<<)dv2Y~i|d*ZW{U5!yIR z__X^6?N$(PCA4A4OE-UHN~t=P^g4AstGnF>FiL5WfM&}%N0vcazSlrhNBBG#TS+&s zUrF25ip1z(_{u$oq$%FpXYj#8l`L6`OSbqoj5P58^q1M zZO1TbF(g=-pF%c2lW7yRskCq;HL82Zx3jVQDP6_=G8F*A|46HDpIFmz6b@U=LsDPE zuO}q9#^EcWx~nr7$D$nk=77beNZ#%H;oB`2c*jfdb-wH1ICq||G-?Sl1Z7wo+MxEv zcO9N}J|<;4(?GX(q{sT=!+1|@;o^SPCgD*?^iyMR&9a#Gnz&I_OFlax?50$x?I4*H3lYWdHM(2w8@m;sS_7W;StYDXqCsDKiMyA)(>R+t zFmZ70eqHaik>-tDB(~Q`;3DGUI9e_i9=EHPuDwM>Q92c5H0t}AB?lA;-0CNq(V%!W z$a^&oE+?y#^bHsYBNdUyDq(x$i&iunTR0smo8+bzWA|{JKAe6p?)r#nnatIZ1Pk|9 zI*OmgoQL8^-kEfrN5CBw5b-KdyInj*B93MAx}@)Z`k@LVTZk`>He{*AO*DVV;U*0E zMfZE3c%gE)$L@AH-rnkcH{|)05>pX=Yk|gJ~e_&l7QmIV7PkZo@+X$IFj4rqwrSAaJ2#~0LaxAS)+3Zq`&A0c;B!gSXzpPF8% z$$e}tJE%#ioY95m-6*UeHu_eSd^imZf4&OUKpyqb@b`8KoA+wWSIdQzve!RtDrwHi zzP^meC|{{!WcRLk>Xx?q>R2Qm!&nVrBhoO)lWbxI`s_P=Ky=_$MNH3k$y^Cwgg^mD zv#3VM(VoEXmwoXtflm{})m`cIiK}U!df<2>o=8yu8t1pYfH-Tm+yt4yWXcLxa?mf0 zZkalD7dHuxQGyG~$(*>r{GsPP{*XL(W@&D+mTz{Z?K$-1vbKy45n}1PO8zVsQ%$JO z=3Vy)8`mUI{0?1xPCy#aH(0fhDf?hDit?8yo1S@Iw)I4gce7AfMI(B{{V>w=yhCyE zN8H;}W!cFaHg1f&OxMIfv6KJN({?E3w{glxH4=C3ykVaVbj@Il(cFAlD{~R0T42Hp zM{GKb6G9}9j+)*=Lqk)~w#MZ=mG~YWJ~n3E6O}$x6Wlej(Ogv{j4UCwXK#h7M_q6< zJCy)6wpqNx-nZnO+`mNX5ePcT#T_NiY|fKy%rAMA>o)q=8%*E3=)QjnYZC}_M1h6+ z)@t431?R0GGG}RiGVC~@Nty2f&`?h6*OE7v8`u|DEXNbp-94%2OgtaR(b-b~E^O&& zyI#>NBmI8e-^A)4ihUuUf}@?jIYcb+kr&PdUxE>H6e1pUtEGCaao2>tw8jGEXrnE# zBVzg8*AMeCh!l~Z=ObgewdK0<(S_95mhu6-gr&-FU;Oyjw!50_k79qIou8IIq%ZKg zh>h&hsD~jpIrp)BJif=Wd5^u4R#JFn%H9uFYfwG2Pc^}2Oq9}AkTXx0v33TrpBf6e z!oz4r^YGLQbfdM3(t}7o%U%6hltIgj4u2Wh?xSgzdZR6YDO`m0%oM<4XCOtX%Z=E> zq@JiJH6>8y0?)C16hB{$op4O=iU zC7EmZv8)Mi=XIjbF0k?q?;_5N>Ed0***7`b2_!yFTHR0I;4KNRmiEoel-!6f-4JVcC5sHgxK2!SjYdb#suCZM zJ!;_47JS+hYaj`6v9>`&mK_12DCv_OGcjbCvp1cJ33rY;%1C!LRdfm>zLdB#!#J-qaLm}`b!OekTCr`QyjCJeU5T`c)A)I^lm1;HDw!PW$ z7%AoKY)k^$S?Q-TlWd05=@y)asJ*)*0w>pw*C3% z+AdFqOeMr8=1>yj>E48l5$2lq3XHMBE+=FVaBETVrL>^Zeno{^ehQ0bGt*qs+loRV z^qu>yB3ofBXJNWtBu||20Uz#7-OgoL4to=$qS95_H+-_yZ$omfwq)h?!v+>eb3T)a zRg(+Gk_@QgZQlLn-0Kh-n_SKoZ19XhA>c|=1QTmwPHE{hUvZWazY(VMjrF4u2na<)ElkSaf;$)Y#A40gfVIZwzXbf{CHAWr#U84v)cW z4IaX^SaPJd@o?fKSGuTJL)SEdJGlM(phxa>x{-L0DDe-0{?|MA(0zKiZ-SZ|oCHD*U47Ua87G8uxbgx% zUfEuS7LOZc_2no(M$y2wFpYYC?-MuJ&f=I!w*R9h4@P!4H*Qow#*JEQMr2y(UwsZ# z$QtW0uo`&fG7^0fu*w^$gw{143^zw8o*dgx@diwLc&h?VO2M{%OsSSuLFK;cagWJr zyRFl@RtxppEh8F5u^X$}_nP5mKFO?UU6)5U8ZPs9L0v3)?hF1bO=xRt+w zRE4%lZeR@~ikG@QLd{44T>5!WNP>`ChLRC*7I7hQrS6W>67!H8C$Tv$b<%g3zzSD% zBpU73l5VddSc1tQ%!fh~or&;;2(!A$S2E|NEK@cdvc@n))-y3R)cNau){L2`^!j$h zBY?4OOKBvnIzNIcTxL!_?c?^xmC7i5Zx2Om`Gln|VoUtOEaaszBgl&u<%N>-YY2-xS=+#o2{zPcg2BT zIqf5MPxL~(Uz^9@0{w^x1x;VIX>bVh_PxQynh$?)jAI&GGDNSNQkN;YG{l`CFMJ3CH%B~vsNFI%=Z5C2o`uWxvELrvhDcjrXDB00qB$HFK;$LyzpuG)iSFloy+0h7Gn;PP;&JT`yuIkN7FwcNrbBk{Z6c13ApJZs`k;=RH|QJvhKT>A&i%Wa5=B`0ia@ zTLF+U;hNqCswb!1-nzHVF}<2!mr_b>m4cWNQu0Ae%2zg{&32Xdkz^vOUB!y|b>wYn zdpYI62p|=XR8oquI7iAZ>8Dh6q6i_WriS8h<(RC}b7h%+-jPBMbvZcLS^G9KX#+Au zH}BR9f$jj_-`|tT&P_}LYKBoCnX6pea;sQw>A#m`s(z+ABjp&Oqm(w$_C%}H(LjA2 zZHmHE)f7j#QoQ2a>4-K@a>4|>~r--@`SO)&N2 zRkN9kCBI60Z;rSL2GgTk`2O$~{FcMWH4CmnUefnv5}<7H$7@xS309ElSE(p_&-EdO zMpd?y&k4M_0kIS!b(+<4x<!irom=i<7Q?g$|K+l(}a`}jO=+W|f7K>NMcc{(y2jR$+@>j00t)Pys z1kA`lLjL8uWr}yJGg$D@Q+uRY3x%TX1KNkU5ddp4TvrE$LFdwA48Q?z^iEFOb`%!F zn9P!+H!W8!orl1YyB|`SotLyOpr)@ivc+?|R=ZEQzIf-2@BB zJ~bm@Sw>bBjQ8XRU(>(Oq~*WpXfbo2IhFHNe!R>>sI-lJzFL4_fGas|Eb{Qps$r2l@JSSva6`ERue$;Pq2_LbNI+=DuM|O_w@IIg zERL@h>8ZB+5=GVc2@xvB3uUrvE0g_TP#qr0KgUGq6RaKuB*eXMK7U@=pX5byp zc6pQ<2A-AUAtwFD?AZc7w*hiVzKJg4(k(-d1gC~{oF!?SOrj~=d(9zEC}k2UI46CQ zoJFKT;$ugPXKwWcryfNQGZ20a5hDpmki9413Of-;Q|Szjisg=zIyEpWE~+0eciC?i zxvt!kiQQIImq!q+jo*9)Z<6AMC|n)JyB?3ql#6ab?Om~<2*8)jiic{RMMz9!h*&ZI zda6D^xXNJfXE7ruQMkmp>zc9(Yq3#zJ337L-tC(D)E@?$-2sz?^91t?ub*s+M&G@p z@_-W*{nvvI%fRa|Q))kpS>N*iEJmc6?#Fff{MFFayOs6iQ}c(dRO2w#)%-vk9?M4-LkS zPY~8+N_^^%?UEl6KLSo}U609>%Xwj<%aTde$Sc<0daL`ANO#U-u6zh2l+oN7YxXBD zL0wqFc%a1Tkqyn54b9R?uEq5z;&9REt9la=)#iv`Suu&jjT}j|rk58}UR>OAI^zvF zf8FoPGFy9KEE$c%9?^uW_>7ADA=q)E+4-7wM?&Tr*`7t=XC^jUqE}BqL6G!pGG~ z=^Ks9&x@hd2z^lQ@+w%*9(sDPIZUL>6~W^R{HPW-cz7P}+r_04SA*(1Cob$b zl$v1-;a21Rer-PqPdmF=0KRbN3WK}ts)q)GF!lEaIA>*79YCAh^?-eRl%fbtRz3?KW(E2b4B^>o;r$e1Don`WA0r zx6L`FXr~PV?><<{E3qcW92vd3dlRc$PHL+mB$_a7ht+Qxo}C*yC%%(QoFTsoFo?juQ&%l!%qrMz_Y8IM<$W z$vcgkzK<7fUM1FIt&F3p9#z0+8r<={T6-TOmGi0l=k*>HOe|^filcyT@V>LM0ImFN zKRa}^@m3p(a&Lnicv@9&h8xlP|FHMoaZRP${y&aoRKz(50jZ8u4NZhl1skCkLlOcZ zfOH9j5~?(3R8&C1&^stKg#;;~mk|Uc2nZy!P^3d36e%JKelv6KoO9-TzvsU0{p0t& z_kMpje+Lv|I_*CkrGGzQJS{hd|2T7FJ`6XO!?$MHD*8@u84u&&J1jos{sIbqdQL$ zajZV?AIwJ9c^$mA)h`Jz+(sPpFBeb8f_e-jl=`Bs9aM{Sw0TtLiGU(niqHaE9`I`l z8P2gMV|wqOpEL07WT9$XmCjsTA{t$cQ1$uQdI?3hX2irWyfuK%uXeQXtTyONv^iT`@7#pjC8L~qz|gtBKX#5Ad&(Z#<(8=u~+SJ$MN z?)5`VQcrg@A8rpmsH*v_sAN_qVCjnYCRvfJssV~heEgF=F^|o1<_xs<2npYvt+Z_I zYOq_=*`per+Y+6+7vVtYbB{YoVz?Gph{#hn%}W!fg)P_HrMz91aN`J&@0=-Tp{tYM zo+ABIe1n-^6kVsU1o!fSWe02v*D(i-ZJe9QhCYp7lHpYx1g5h}evOv-g2chC$DgHyycw-N&IkqS&%5!4 z*~QTvvNSaYW(Ae6H6*RwT2$`ppPPi6Hw~@Lcg!)q!ZHa5O-P4qomI3`gUhTc21M76p3^TMXJ)Pyd2ZIP6JTv&HwJ4I zz-1gkssSU*IhU|rv?F}>IqAS;##Lju;C)lgd;SN7G? za2r0$(UJ|ZR_S`E31(&1clSJXKNFGKZ$flRk!fOGIu{z`EeDZ!A{+8U4*V_IfGB%x z1#XJWp_AN?mpSb>IPS6nM3J$Jofy#js>FhwqaEx6f^j5Zm$q8neBlNv*RBww6O%kg^_xe`DpIm^o6X3p zs^!$m5dtOVa1rTb#0F|2&pzq zx5vtcdr1`7HMU!oJ!;H}DzV$KQXNor7fnyIVt>Tt?&xOr3v>mpQQ}{x;wB`d70%5= zL2$Vb;OE+}XI6`^WZVJ-@sZcI*{{$%gmH?N8VD<|O7AJD8`7)QZ-ZDdIB>so1PH%a zkdy7(DfPv$|BKZ4=wVMlD@y_VgH_(E>C&G=KrSRas#>uS?9Xt>cGZ}B_H3_sYvZP; zxP_oygiGe!r~$MD%*WjYw|G6s;C zs?~_ZpKUrfH{azkQGBtv_~svXW7qXPPt)YpMWwRa#V1dx#exYvf`k* z9r$U|86Wd%NhF>EW1~1+_%F&k@u??Bg=9}kvcUen&T6wp`ciK6jAu=Xjso;v8_{V! zn)Lv&FI&WtEh#(a=7dqU35)8V|%SPI3XVF zSzjKtYTsKS=<-FW#w*LDx$h>z#xFCUg5F=r;neSEjuGsdq&OuU0Il01%O z#*wr41Xr_G)y*~BH zlI$AYUz038Br|gO)O9E^n!3XPDHQyi1l+=4 zslAht8bk@@5Q+S<_)^-X=w!BoP<-_?Jb!7M$9ZSmH1};8TFRuU`Lo7aVHgo-uwj3R z(58uUSz0D*R4o*5Miex3Z)eXKfMYe_?EoYO#umqtu=kinZX@jACn)v?1cmX5mI{>l zcw5qCY64~eBm%v)uOVLbTyE2OA~VPK9i4DXBkk#wEG75k2vnR3{=EB^@L`?AfI}b} zj&QBbP8no}|BU4X40?VCtlFdertW~-O2Iz9HQUSj4RMSiVqW>h>*=3z^YSPDyh?Uy zl=c4t*oy1;pBBJ^Dq*&PClX_N5cb_(+k^6Pi74+n0IG?YYq`67v9i(DVvhk{a$h{H znw~Lgdc5y%4Lmk4OW9bt9k57xY>NJx1TlP4IJN$2w;Ad18Q~k(sO{I9LuHR8T)Wq( zGUGtfMQM7-$=1*fASSBGTOD;S<(kNavN(WN+$EAcwY5DPNZA!@F&t&XsN36ByzR%s zvRUbSL?ce%CpLu>TR1lTW~76Ig)0#Zl&l_GhS)`6%aFwBt3(PMz7N|Xt1)-tt|`XX zW73}ReuQ>VEXqV98&k+PNlL;*x9-5Q%yQdJ50&2DO@M z3mZ(1qFBLl!EkkkpuebSET@t$O(*Z3oo1ziX^)Xjm9`mNK~l=Ee4LTUG;qTJcs(p;C%-t{ zUt6MLIU?1{UdyVs5&;{!5$EiLcRoKvMUZK$K8n!SpVP`J-`#yq0!NyXD&@>`8a?W9 zafG=68Qms`wc7JA!h>7J;0lCL>*^)V--Zd!Twx!)bn)X>d|M??|Y-?6wqqak|gW6dr;5Pa1 z1T482yuo>ywvsYyC-!>atMJ+9mluD$ngDAX&{pjqgSsInJ{bkF^C?1-iNe-d+PuLC zoJ-KI$YaxRRtto;lBWQPsX4&ht+NyJdf5o?k(M6FwWCL``DZmI%C^(w;9`sqoAxrt zk1EN8Vb1IXP`gSn>cDQ?D2oJ1(9VZ4y_HgJ9QJb-wqo1Lp``c@Q@=rTl)N@%ge{O< zJLTb}9&5_nXf9vsFpO@Dvj=9F4U-Vu$*^AK;h%q^xqS@%{*Jb}N5s#QstkdWnn`bY zLR_EMIVxby#u;0P=|$0fSV@;(hrYt+MslUc%E89D1UMxcJXk7(qchbG;5zz#zctT{ zJ@&Bkz8k5=S}|ZlS_h*L)=$jFHsC3y)9?}dBATBmK%r?x4$PDci!g=Nv23Fat{(7wYSz~y@^8<|;OLql`b~ zX}4g)mqJ1B^+WM3?PX-)0U1kAV+1qtGk3L(_Q>lSt0Aye>m@NPKbS_&3;mHpr0n@t5Fd~fwdU344y3_93{Td`gHk9ZakcamvY26pu%B|S^+L=d(ms)OzA{F>x zHC$&6Lb#3|!PX9>iA?@dGk(u8F$9yOYn(m{h8vEL`^>%>_uG_A39Sg(+xM4*uwho~ zAn57XvHQUo(KObS?^<v3SLX)Fz_2*Og_=sg4-W*}L_A5E}HG&>z z+K=sJXj-Ba3{SRka&*Mj{ErHRpxI>hmYO;o#I}qO`McFT>5Rxmn75lBzd@30X8) zsM?MIL&VPhV4@tW1y|wIw(#soTj!Lu=vK{bIzatKcC%9nsAU_zX>=X7;d#P4_f6!W zGJ%M?H#GCuTay z+)sZN?R^`n#{vP@|JhXfukAeA{PZIWVuX*^>>{dwIX7=dx;EpQ^%5zk?%IfVI`Y!m zX%dSCxov6w!B{-1dZ8#%$vp8@zeZ=N~e| zt`r0l?0d_CmGYALKFNfYAS;UJ{o@iCjKY5E(l)xBJR4=Yp|?qB6d60e!co29;yMC| ze3~0r$yr@rbDQ__*<&_Io?L&kQykog3ayW}Wu?@hgS8l7yyjPQ%Pr5a9-FEqeBEpEHY-(dXC4(bOVDwlx^OJF{KvG3e-9gsJj6$SG z0sbXJAkD}BY5`s;^*&VmTqL;`4yO4iBZPX-jNPe@D<^AKl|UK8V4dazgN~R3bcV;u zG6JVA?E^5E`_P~;)5zV9BBVuWei0u7O=hIZ4bd0VO!bNJxy&15wp7#uLJaaTw!n{G z<_zgIT`YUTio?9z6%h>;ytzEN%7U9MZO=#HmupgZHOK1GE(`m)xun2HU?`xdQDsEg zq;)Fy(7>gagYO*9Zo1ZxK`e6i1A#?g>7B9*@xng7pXqPMxZN^++^nLimm6SeOZE+0 zh8f|EA832nx2tt)GmR+H@Z(nQkI!)D7m}RzlROxvwQMwtmU#?obRxFWaEV>+92=oBV1^i=uLTTnii6Rx&)pSj@qD$C$$IOZN;PgeMA&fuosz3Z{>}#VZ*2z6EGi9}m z@P&S)Lrd%yo8l8&U$>&z&41x%E-@od%0`Bv9V+ zE_eKs<*hi7u2sdA@kYOq*f*=Me`Q^nm4d2H2IlS_@(t}A;yN=rNdj_R7L|H$n4p?m zw}5BOeYd?jO+45a3h?);TAsRvh3cJ?A}MK%UQ@+thW10C7u2|+0NomZ+E z1?sy6&&vwQ?@iyJ)|$#e6bk~xPsHceU!{2=8U9F83`!B9-cebv zMskWPHSng^%{1EYAQV-Sr`vFMVY6E=i88vC=I1R((y7i)=F|`W+wJo@Oym6|y3jr;HlaJfbw=#Qf57VhS8qLX(WNd}mjLMA%h1r_go7dCCnvPE z8oWlIKClX?wH?7V_SbBNIIL|h%3T;7%hMtTN~6>k}wpL6BB0K z7O^x^f)Ac~V7YT((X$QEA$b|cBMWOxkQKPCh7{N|z#=wGjDzEn8=N(9oN?JjkESXL zMzaSSXde=qmse6V%OZFNa7eAxzzV$;3t^7XQ*ZXk*}6q`;ZNUj@&e9fx|+&rx+U^` zG#|{*=;_J-B<}L=-K2e~o*oBV|9eWi>TCQ_bU9^3&!LD1f}bqZ+vm9fF7V|#_ix-K zzh?c8VNTH!m5_o^31!2tmDBz~(?9%AV*gJeGyUB%kA(i*+De}KGXmh=ZzlfM77m=e{x@8}Uo9j@ z*zRA9J;-RG`8AlbuV!x9Ga~{e*}EGq%>8xYNSW9PA%w#@pCNS49%mtTH&C?9wG0^7)mYK8JFHV)M? z%u%hV0|Z;s;qX>Jd$H(tWA9w1So5dQYFo5?4M~Jj>d)SqxNIv5w<8J^?#hURmU z35~ju$JUy1Klbp2<3cT^1*sR4!HB+52P9JbhgXu-TljJ^=kvnKwHTUAOe(1M4Cjo zit#z{XcwR~X~b;=h_7`60_Tg#7yD8@s-P;U9oYP2%D;tyBD3oTt}@I=&shUT3|xCT zDsmUNPmN|1FUF}hQ*8kaXsZS5GIis+9iFbsi4Pwd&19_WQ_q*7fyxkHQWNfUOa@D% zisU|O|32WiUrl{A+D@QqN2j+W5c&ekP^J-wTxlId?|FQa$7qU)Ym`5&zsjz}fDoh< z(O~UfidgTGsMggMoK18WpPW455(ahV17aQ?5$}dB7S*ih+IOfAF38{1?a%_ zK=N@_$^9gI;;!a35L}3>iBZ^4GAwc%ow#`H9vXC}^W>YFd zCp?GN1RKQzL#6H3a^u7dxclD@yTae@=o6>mWZ0(5@^TLb>YqQbtG^VRoU);NVpwfn zZ|O35oCy(=bM=SEV}T(j+u`<&FP~E@l|J<-m)hbVnB=tZ5AnEU5Y9k}Oh%Ene zAj90AcfLVe<7?;P5dQWZ8A5d;EsJ$^vvEEH< zUtuP~#Pj(qXwN)MFoe?7Od zy6iz&tE|^&e+Ru@s0~^Vb-L!`hc2kEPm|~$??JfdbDh9s0ws2e&3NN{pb*{1>Rhq6 zO^@rrT8r<{k5&7deiT^}9lZ8rpNCWORpPf90BiV$xkWC&GqD!hUzRp-0ZNWl)A`8IxV!8U8_>#uw82F|+n_VwoY zZ(Pi@4HmS$Al2-|)ju`IN7=UQwcP%~k(oWH-C$iqYPt-Xp6{EsTK^kaMHk?6Vdmh6 z^@49)yR&Wh&3p5SA9F9}rFXQP9*+M5@8WNTLDvgP61TTB;~84GhMZ8@_`v>3-Gpg+ zNb<*8&mZ2;BL**g9D_OSDhevBB8B-tad`72M6#|BZ zwikMrh)aN1bUK~nk`yS~7AQzj4*YPe{Vqy!F~mq?Wn*smhiSk4g3@ ze%{vAN%o8O!Q-@6PCeoLH!lAR^`7GWO6)MWCuAu$1>O2ispoY8bOZq)4g-OX(1-aC zC#v?Xmv=OWc#V&nF`UAZS<#eRHd0uBl1Z68ZYi-w*VVrs@3s{T0qDAP4j@#o zX|*6if$7Em@1|%0dyR#s$j*hN0Q3CP%3-P|E7&(IQa%pWjp8Iczlu`aUzImj#y>aJ zY*G=7{&>V^bhn$*ppRpH!GJ|i`8Yc$8HX_&ag!ZuD|T~A_S&a^^7Q_okfk@8Gw7DG zot$R0ESp#?of2#Ki4#%w>%32%fxY(h_>t?!2jd#*wGSK;wh_cF>1)$dOZM&-D^SjJO1srB#Z!V76PkTr70fnOk%ec3ALu}>W z*l}@5-{CrO^Uss?J-@E0ZB8UFkDWVupeVVf*%RBST^~`%bzbYIKb^#X*3Ngv!x6bt zAG9}L-9Oz3U9wc=RgM_sbIqOhE?+?d3d@Qg)+;ungZ9iEL=a@bMX<7W!CaFM!GFHO zPC6ex^9!sd$NfFd6ev5GI21!hl^mD4T*jb}=w;YX@kciuxH^M$SBtU3>utwSni`(M zAnZ*s05(V-aB_TqX+WppyJ6#6TRCJd!sO9~ZXG#sN`|hEw%B0|G0fcj%U9Tg`v>~D zin3=K&*-}Tgh6Fc!OjVuob3J0-F3r?>n#1^>yO{=Ba$L>D(aF@^O>KtL)NRX#YF4|@K&)>m);r1uxfC<<&CefX$pMwLItvDm}#)5Kp{Leo=$%pS{7+^1(B z%&n(|N|NIvhJ8RrEbU3LPj-zAS^Rnk?x?)4Tdt+fAN#@o^3fTMGv%){@Bg1nUF|&K zF)#Ut>8F2~l>Dh%^nVV~^bbD&zwW4KVtz#L6fHMm#_}-;y;(T8UZzg0L8{cbd^#k% zB;estw)_vl&S;NZ(u`g%2)=z$bx<%4kXbxP)YV|}W1M+~hl+Xs@}i&BW7@fxZ-@M^(Vs2Kwwo<$r5#AIWV~!` zE2h>|t9)-_fA&oOtgr=t-*pDw;~AX&s;;Z^^+xUN<#vs~s2Gr_RuvT!+RQZbqT`h2 zv!T_*Qwh|oh1$($e6V%T%D;M3XOqo^C>|IQLQbiwG;P|c(E#FU^M&kyZBi?$Fx+?V zY&nD4N)T#pA$Y=hA?gV3Cy!+MF*5CbH6Ht8q0!PYUx4j;^tj;c1!`~P$H*e#=Rxq$ zR{^CWcI9YovKT-`ZpJmV>5?4q-jtY*H%l(3M+X{zj1!#B_w6zpJxs(u^%N$=r3#hq z3dsbLW2R0Btw54Y$7K_qJGU$KV(;as5ajEhbkMk^gEC%34-`#i3=6=Jb7(3pM?gBE z&xynkO*iZ(B*W}qzMvsKY2^+=5W;H~h73aV^3ZT)E7*d}-3ZYU|Lhvtt;)$4m_Qd@ zusnQ9F=Gd+K1YWzkof3j%YCaLWX`Wg;JcE0> ziM@V8W8t!3m~GyncYFL z_Pgz0pO?4#{|hoLiGxu49hqhUnfwznjkEtd3GBCzzVZD#GVOme^><|2Z>E2zvi)ZA zAE|8ra|q7;{0Fw&e^p0q;s`|P+M)3k7C;ORu~D%n(4TdQi9mE+^e+Zn+NGRA^)b}y zNtb8dt-}tFgZH}!c|}x+ZkRxSG=dE-4j{KK-Yu@CRl$-NbR)k+Q&4$jgm8!QHBb;h zqYj_WW=Zbg1_}pkuT|M!=s!0fu(Z9=Tw#vRNHXr3504#s?g?3gnaediUg^3!2A~i> zzX;T+exevzT`>x^IA;5H;`0T*OgfDy;R@FvekeuvT<|X)2)&9#o?M}Xh8Cxm4}F&M zyw84{s$NckPZ-GTZov^kXg^hmyrvivh+*xRO(X^;4%L$_S%)taD#`iT;kra=}c!c5u) zaOF*ck+imBxW9kAfdt4J2S#gaPh=ae$t+isjL@$}vg~*|*g<&eYX_)Ub@ZT?nr3u{ zeVi!>@OJG~yKn8Sp#Cauv9;;r(qisftXNO)_ixEo6~?YXYx}^xG$C+nF;qsY)*}LX z0qX(8AWVuX*yI->>v=pvfm$>OfR9skbF;(JWK<)`?*;S5&Dm0^w!-~2XRZTj-+%k>TJ@^zKF9O8ZMY$BPr^eR8O(AV0NxZfKxn`W#JBFeZZgO~=@vevd`K%Qf$; zS{QV|Rs?+8Z*W+ke`rw7uJy9|oU?-fFEPVF0;sJpORXMix6=-g{Zja zLqB}dzD+rXc(mRhW}vLy*=Z*J?tAm@(3f<9w~A_Gsqp^ZiKY3t5T%|ipwe;hOw3(& z4Q8i$GV*mXP@T9}L7W!;#)aLi0DWxqUR|ASS9#X3Ic2afOI+E{v&zqL4&(50XF9VB zeFDd|UFfb4W+z5^<;`)jKa+=ARyQ8BI&(HYZs!lh zHVM+MTl5iwk_yTEw7AR)aaZxR;stPb+JYYK;MR0;_5Fe!X~m>0!*Qk4^Ej=BwmjLN zJ~^MxB1S)+G+mp$p!IT!i7SjXWIq7gErOAu_flM2T_G_$o+*4(2mry7*P@6OPAQ$K zy%%BOlVxrNqC}(KqJG)X3w~T)hsvPs5I13+XO+i<&C2ApLq@C9`$cS083b>C%jN3U z6yFxV^+Y$~bUCn`Nazex?1Y8^Oem@Sp-LVVD7NZ8aJo40yqz?F(U*2kLX%IfsqGph z7!md)r1-4a5j%j@_{v3k4NJ@*uqfX+^z_i14uf9F1lk+@e3^Zp>!j;M)5V9VU3ZV+ z)B`&Q-STEA{kEzS26Tp_gEb!SRQlXW!Vw=Gf8pty*yK09?G5MR97k~s$sqNN8@m+U zdouE0lpC!D@76zb8C)~gf>|L313Ed-;HY@?(PICxYl7 z;6D1Dd&m$LZnr5HQ7kQgzl|Iz^ir2LDvbQN>74Ua6LL)zU99|iAq)0?Na6bzs-+4q z#~Fal!X5b8Ot2MIBuzayEby8vRVAK&`FdCCyir5}T}yF+o|;IGwh{Ga!NGbDzDhcL zf)%kp!9Vp?kup0DSm-l%+GjQZ9AGrAZ2y&cO+}?wjq{5m1%LUYznCkGmc_?_P?D2z zaElys73;~R$ziJR0tFK3o9;FV-%*T5*eJZ)xa`3uCu;PE3y=!K5T~~Fr*4ig7d&^} zD`S(_Y~-Z!y}y!%*h@}W;(^CkpgBhNHjF*VeWG|^huhuW{O^A~D_pNZT6QbWY1p7KoU^trzvC+Ah>d%Ewa^b7Ir7{X)LJX!48D^Ja9)iCjvZ)ooCF zTw!5UmxN_O!dgOCyLyU-gv4g6yN1FGUX{Rnd9IU&zg_qZ^jIkRab*B;7(z|EB;G-d zS-ZbPYPfK=5I&pRGgX@RBM1Xl<#t(I+IODvkNRbv7R|>H+v^nzFj^@tuIIjyw`>S~{WzYaYg}BOhCj!^)^>CB{N_a)b5?c03Ffom0~6k*z^}$oP#d$)ukW3$d2Pm==eKV-7)bx_6KmLfrAUB$KR#z_^Le1F7 zj>i&Z(O)f7Ptu#NDTwITz4oUIm4q6FS2Lgzhw9!k zR`l#oW4L(iBnyM==dpX^b*fxE)}QoOsWirB{)hKm;6A|23$ClXy@AcR(dvxMbwg@O z{Xl-khW}Od%`3z@Uwo}z2QRS^9>ymGh?~BSoCG&dGn}|-$ z^5Cbz7F9nT{r=A1&HQ)Y(nCwvN@ALhu;WN}f=2$?Lx-|*$+W%Uz$@7Z4Qbl$t0%eM zGYWd^PqB+fXI=c(OUpK1O=W11Qo?vi%!kea=G`Z`9{>7RFPu6p!ebYJn&mAo56zL7 zmGx-?_Gx}!y6%X2Uu|>iyFYgC{vrLR@;dd-Pk$2v)E+3^`X#h?_soWC@{clK{$=m% zHtE`&cxBV0YeKFkPc{Fs_b-<3_MP}2!+&-1|G1**QIpp1-?!ggakSFTD|Z~9NWHRF z0>Z`wM#KKitfBwp-*LJVPfTQdP~I1ryk7c|f5f9$-Dmmo)~2R$n~1j5&ysP0I(fZAqsI) zMXu1PfT(i$m{q-1&ESnms(wLv?nz=y6Zr9C#@SL&g$Mz2Z^fmX19rWVD&nCP&FX71 zd@+k+#)TbAIZ1cFaWP}9Nq%iu_E1cK_&j(Q)p3|&GEP%ifC$r%&p3olC`)LOo4Ji)ES)_wS``>&%KkCvuD>`SOEs8kB{7Lgy zi8bUyh6~|EaV?5)39ZN%5UPQ8ckv;loo+}dNCB+10;jJwawUI%ERfeowfw5Smi1 z!6*s2BvFmoGlzWS4RX+D9YOpzt`O8WF5@o-U!X0s%BGhF(CMT8dsYqMxv}&Ijp!bd zoqAGsQGvh8H0b;s+?>AGE-z3e2||%Y^s8@r(KSlqBwXHC!=0fgad51n&T&*7+hH4Q zaY&n*Z!A7JLb`v3U1@Ft*st%l2x>xE^;uZb6Hnipp2~O!`>^N=5y?Fr-R|4;g!|`r zUhix~nuIk0J?3=w?PX9sF}MCLG4&o(%EGlfQmY5nd%X&q69B7w@FUO0|zf1VGN93zAG zditC;!C-c)D;j5@Z07D&eE{zp(#%>7sA_oCl}z(908#~?ieJB}BA!MVX8M#CjtOiz zzKnCP7|Td0Bt(l%Yl`g`SsX`=yN3A=tz{fuVv6uSM=Qs1N&?dLEY68XUbl+{t3u7} z8PpjEyQP+agYTiSbSJ0jFM?EM6=h}2`qtQbw$@XNVGmD+D{=n2!C}jmtVGZ@M=~Ry z^VrRQDefi?4@^$6g{LIbhK-K%E5p1$zpEQ@$P0XTWpi6YBPlw{%wMGK;{~H_dO(Dm zti@CH7<78{o;>opd(A-*t9S&jg&ioc^64}XU)v3i)K0n%2yN7h`{IloG=1M(J?)sQ zWhLVvrG#^Fl0`Q7^|pqu{MwbBj4CY|!l6m1UvA(Duc43I5zb5mJs@~+CjJT05TKqXsnQq+2Y+~$!GfcTwYn8^NA-Jl*a|Zh zZGnxTOBWH%sb(ESMF4V@B&cA=m>%9f^Zr@B>(58hf*dO_>Lj%=EIpV>wj)%5Nc@Q% z3tJw1wtQ)?t%#(wh_~tXRG;H4dtn~lO6KmXS_R#bwaC=`VwaeGf~8hwG(b=xTAU8= zSQiC_5G7>!+mutSUkK%W7}Q&dyAaWEZ>*t62a~H9g!W9GDU8#v8=a<8^c%Qyt7e`B z1cg;Fk7bHSxR#QL9+*?I>K4akJ5wM`BIz|8yP7ZIJVPXYaC;iYY@d^0T5FIY!19rQsdh^~_g_2N1Lk57bq?gPkUd>J_7Ek09nnpJbJ&VR^qIferN<5YS zay7Dl7lEl7@v2WU8iJu#gN-zwTz$#w>O#s^e<%||?RxHOwL>wHUUALR8Ql>(-eNWB zeL0d8C7nROgu|U776%6-UekbnGztLnm+bcx8|R3?O50ox{Hl3YN2<#&+ATJ}sdobH z{S~uNEZAyZnsWQv$CBt@f^$T%QvDT)`qg?YtgL#}{<|}mvtGkovu<$7zxb^~Khm73 z7#XA=8x@vNw~sg?hf5zBdc_zT8dpdS?$YP1TF6&aNImGKhb6#u13p--kA=<`tznsK zRoFX22AlZ_+S&jP_a=P#*B(@{52HTlpk&pGSRWg~BX(2!)wJ z`VIMEqri>#FnJEdD>bKyGKB*Yev#XdmOX&o%4I`hfQM>F_3G;z->v@i|BL0Ne?6j} zKBUB47IQ|>mL|Ki730WgC5=^0K%M=`EaIa3*KAq9tzQw;NBIWUS@PFb@&ckHgIEn& zSjn`oQ^T(T8aqBdL3|tprS*+tGJmygdi4g^T;Po(k2G(3c2i>^9yuBlH+sG=QBp{u z)|bEc$Kmv>)&})=Hhv5nquBc$SkwG`_`wTrGpg(`W4g@k-Cp8LDiAhB1tTAzJ(#yO z_eUGBr54Y|U4!!;w<@YU=idu*AP05q5XU0gYBK{@(zn4eMUZ$Oq_iv4cNx*q;g~Iq zZ@TBOi>ZiEsEpARyIbJNBKAuW8|X%TLkP_f(tJ+1?0mSqdoTw^rg#7@t7Avc9guVlNbu!-SPVbn_3!h_e${bD@?kCvF zKu_b-yV|?D?+G@&SR#%w-<6LGnfRvSo_yw!Ty}1NmWwpN60~R5fpvo7XOXos&L*!T zF!B^G;47}9?M!|qSgkvt_x+_ysubLg1POlD8XWmkPO>w~45>_s5ZfR7KjESj!& zTg6kZr4$xWhqWGsm)0@Wu<;SN07t!ILbd!l|R8OBd8@*urZX~424!|YRt$>d@8elGPb`i z8nS%mPR&Ci&{c&_`cTBQxHqB4&cWJ7jfJlBfHPOljZddOv@+5gYLf&Ef^5^7*?vKM zF(K{wJT_r&HCbmGLYN>if!v3KfiDu@r-`(}>bkKumjRokbV9=gwHWe7q#Cx-O!KCL zJpVbb_F;_9iQ?tZu?eu=B3w)HXUhdzDZz|xx8ugz<9;1ny8~!asvu(QY80Blr0wpN zZN^yGnPie*QLsMA`rfPgI9ht-?4BZvBKR;{+j-=m#pI4x?(j?1`~h2!pjb@3)!^7= z(S=)YaBf`?36NkQ{@EJ@2TR5z6&fD+6IHe~UDP4`pGHkDi(=1cXjrIa5 z(W@sccwlPPAm^`8M^}@S`-lEQ3&SVV56m~+M?tzT21GoVn2aGgvUKDN%34k)Tl(k( zCgEI8aBzv9l|(@bo@}p^b6+6N0R4@NrsN^)qi=;dJzcSf8Iwr#>AW+H%H!Fr8edp6 z4Ag3?oorgeIAjOSw#p;deGp8m!BwP)!SYF}LLiG)6B^3OdJ2A+)3~;B;BCZ? z*TIo=AJLM;K(#0{gL0Fi<4hOZ8+2Q*086#{9ls)CMqEOLtE&rbCvJ6|rkp_af2?P& z-M!qSQ?j-`e>q>tymH);+F8jcN~Iu;ghW-ydfp56^QLO15|kNAh|+i1!IOECmArqppVbSj+dC&>@2(- z?NJbTRHysB&Ls*3Uw;Ssj%>)1d77bL*USgE?(_kPbrOuLi$@N=aXDb*lAEfv1K~Wn z`4wXXcJS%+3t}MC{z2{6UNKn|y-*NkqIb>YL=LmM+)l70pefBwI8p3@Xu$4%>ZAhp zrbnUmCtKBZ0$5*5VW{cdI2;jjDZ29*Qd<_@W`&8j9L6Zlzu3KBa!)DEQ%4Gp$cmb2 z+O7(a1IoR8`fJ>JcsyFJRzgBXV`s+SDa>{c`N~d)M7?e?wVHOBEEs08XjHFbYTdi~ zEV{YfK%(H)vqmWH>ircD5y?uAUWjlvWF_ttekU@8p={ZAGhon85n;Rdgdb<^9+SZi zy6~i~3kF{8q%dyIRkFYyp;#U?#_T{7K~~zU8_GNJvilTKj=iyF18z&^VJN+@Eg=6I~!?f z14J7}U@#g9QpYKIZVUmnwvmKu+e+^H?ljqn{b1n$jpj>DZ)65J`S72N!QbCXRAf-~ z2QkQ>>k}ih-b+XQNM5kLpiyQ(N^ii&*-Z?US%vHBJblp*;dZ{u_&Hi5aQ#e^*zoke zNl^jmb#x4R?8~ysh1LsymQ{}p`g4)!X|*7D!<{1qSN_k}^k1O$-$?`hlb%#~aZMk7 zHyCVmBQCQzv&c>=j1xk55Wj%^OUw71K+i7`+mW04NqpLJFUrHk9S(1P>i^PFzPfg} zNEL`aA5)8vf7crRRTRl;S82V?=_m4m4|c6MWwrBHYB0J)tvJJ>jyY`GQRhf)vBvq% zmv`{bMpf1g-FhBlf6ltq#glj-$x_`)}Ut%E0+VEVpof2e7+NnpPUc$;tnR0RB zPYaqZ(nHn7mG?u*um~BAT*vKDw1`{blPM$t;CwL*&4IiwmHL-ce0w>;6AF>Zph_hykh2P$dXRZ-S%Jt04)2B)|aD0-=YF&Y&m+ zfuRKHXn>GHkrE&TP>~`fAdpZ30Rd^D2ndLv&Np+;J@QDNz*Y= zi8yB}!?C4|){s9V4>Y<(=j_Y!ER%{aSBKTUUI?Aug@)o_KyoWNa$o9EUs9|^t6Jly zJIN~-N2hhHF2qTS1}l9mPkVSxo7>y70RTgo3^^0yK_}U4Go^x$zy&BYngyBT`&gXC zfNpDRTN24w3hJz#qUt@w3VyR6Y!jAJoK!+RHbsh!FnD3JBigranH6B%$E;Msj*Y}RaFGM#Xe>9mk z=aNft#I~z?kV?X;3CQQUSJWE=>xP-Ao6f6xUEO9u1o&x1mz-$_C4lZ;;D#R9NW0wW z3YSq1;!a#z4s*%(a8k-pDmMw0nd-$Gx@0V<7y2X>8JcCE`qc1{jJi~8p@MJjv0U8H zS8y<|bl43bdt+leJgs4Uddrqv#?$52(v?s8m*IKjD$B><7dwkn3x47cRJIrBL-TqN zn|JdaoM-4Gy!~FG8z{Q5C(zSG+Dyn3G95y#h9aeRgKe)}`$o{8#EyClWVOsxi3K#~ z89!+P15_ybpGJFW=H6JoyXiVwTzhewpi9$V$;YtoL6F%TtEjd&?a9|~!V=tR z@CB3h&AE3d&*&E4OhNEW6{LYy`D_Y>Z?sJ@E;yP=se z%C@?59oC;0)W>{@Y*qPQCFk@M5{=De4G&X65^IL5_Xkl8#u0-Fwsis))w@@5=LXhI zFiWNGrl0iv6RS|(01WGurDaQ>7;;Z%M0c)uV&LPv|ZUHwj8RDMc)zd}+KsoD3Y- z>o#6g6I<_?4!BauU4?`~zteE=D0cpx-M$H$$)=w@bmL3e0I%#+a>$hg9D~4-iZjXj6^Q7%u5o9`e3~qGu5UA zohy^ZeK4M3wDYO-^Bwsu>TlevMiL=^lhz;V{{HMnu`l0=vrqmLysrL(Zs8tFfRwJR zXgd&Yw0xtk4p^LQT@@LO)Vr`DrDAl}qFn@Z^E^x2Hl&6bh>md;qiPK3$z*P!S`*(* z+r3@?BWXL!6D$eY7nGgx1NBnG({o&fYMLn@q~Fb1Wf=+p8-aQ)8yh>mdIK%7Hk^&N zDNHce;OG0V5>Pqp(tGCuY2+P9N^igP9$lRHTstJVtV_i`Dnel@VZbA^q^E^k7*omV z%j~E$FFl=-rV~G>AXIiH9LP6zjoZE3k#5i5dl;IgRHpRV=hxklng1&HInw*( z?CkG+^kTtrJ}7~0NJ!l~M0=E$giupceRC?^OVY`SYXrv@*u1o~2eusKMyP&3wi!pR z2nE;5rg+dAwK$ez^OzZX(u2KU%LCxat`41nr5RZhm(JbrSL@8x+QYSj2P5AIDhx+w zKb4f&x2BNDf~0=KX-nQFdjZ-d$e%qBzT!Rx6TJ*ogRB&~*%nXghi^=yNIX54p{4u% zZ^@}%k#0~SIr*{Ll(=X4o=VzrvH6H`Uk^X;;e!mX256UhJ?>Isy6Ra$nvRf)3-!hg zr3MDgX1J{<`bpj-xsuY>(3dQny5*mKrRAON{n^Iw!2%)b9h=6cC)1$)B0xU>Y*a zAN9`Bmy~Z8T9Fi#@&I2+d8{RPrOFR9Xl9^0n`*I_Ub1IFtNW6-lE8#xr7Ewvy>dFb ziRyvfqWL?UW0%O!b$f$*1O>NjmfpsXUM*5-KIqZ<;LH9r#PMM%DdL}|YC9=g4xR%1 zoC{Du1y6^Ov6Ztg=ph)k@|e$o`YZ?ow`@dIsZah)pLCCfH0qX^&f&EUNB}6jsV{`p zefE_3sQ3|9478>%j4j{n+0cetrvU9j!~7pU=J4(4-(@2oh2;e(ORrSeL5v z$y1^2){l-_csE)3qCL}*4F{tgqjT`McfA_zobK~|F#hpO{o%xqxez`tCf~>p=;^tZ z=m&8pF0#uN`Q-=viv0-$40o+R^qGmYT6*lj9g(#D#OFq*CDXD)FFK4m*H7XJp-l`6 zaNC~Fmv6N+R&F}-5X5`2ji7`4Aw!j`JNr#T0MpEt4!eS`G~2zUijk}4l{!T90b$TC zjLK54!ak+<;|i_Di9GH{GB5Sz5-DE82mW=W&PEk{r?pSo-ni0TU3k$#YT*@}+3N{g zF@8(7&qI-kgq)VXWd-8P@)N;}#2&xt14Mr1sZl}Q>a;~3H+aG1_Lx}FgC2`=jO5kb zKnL~cJZ)M3cHlzqgak3iwnb1pdO?J!eT}3@#1C&lCsER7uUs{`Niv!9R6){9MAqAu z$amawd>7^16}&QJmD1C>vvfUS1j=GW;B)KyjTvRjQs0nHpXdn3sjxJUt2`}AYWP%| zVP5==EUfWz2!V%bsCbSKJ~)e7rko|=TW43pIHnh&_iT;MPv_y~jm}N|ay0c%?$UqJ z*q-Prcl~S~+GtodvFGu0v8n};@c{ryvH-MB((77ZFL1)Qfigj?-q|~;@}|Zv7v6es z8dsbZC}DCCvEDjb135}uA0|r@e;Vy6$bj+)qGn3L2?Kj-1>!rOV&S13{jLq~Rfk

6|Kby=ze(P7R`XLoT+(e^bWOt^8Z2oQ8XR2ZDs!=Ao|O{I9C6EH~f8}4SFOc zvX4tOftX#$&4C&3^0sA1Cf{ej|772|{@q^s3;7kPPXWL4;gs&Y`JK1In^U6Cy9i+*Z zg3y>#!@zy+A`>1hJhxawGH}^J>8qd*XDrK)8{0CsZs%}>Y9V|#e)`ks{)6k`1~)~{ zdvw$?+2relO+>>$PDUPCGX~Z{nhOcnyFzRM-OA(p`GJqk6MvgN`Gkr)jl*Y34oy3wT5EQ=;h235cJ=9syFs~mm1KlJmt4uF?n%#1}yiK?&`vnn1PR^Qk zuhJ+Gt$&IZ_kf4`+LlwQurYB5X(@mMmioxpaJ58$(X;5bno9=zdim zfI2>PxhADKJ^O)_UN6khobIpWM~{RP(QjHK7Sq)~kuP??$~N;Hi~!6nxU|TB@0w*C z)UBB>!pyfNdv^Q593XFGGR!rd^UTc9$r6a+i!WKKCZ<&)XG~1mfhuSE0vhev#2N4g%RYUAC+M$3;G+jbcE(6p2^)LTq`IdaOUKrqAvg(z9ui~VhvQh(tQp{PQrQ(e;O+fTbT5D+&k{; znMzodk)t^9z(c$R7=6G`W2=Y;?e1@r4tf3)Xn9@gU>zj8^O3#i#qmx7)vD3vHD8mm zd2=Q3IP_p^kSg$ade4Gz!TU-7=WC<4N% zq6Qjof?Q=d%*4cEZ9zeyc!h7?q_}VCU&~etA}y0UUUIK^1;1CTrdP)VYIHTQma9y4 z*pIu3>)(YITb=6!pYy&avHtFExxkLl)yBJ)y4P+${c4@?I5WGK8;&kR$rEyLjn#YbpBdNG(GW6xU3PY8sNkUKshV2x`Ld zxPxi`auVD6;QTQCV&$aFDs_}Kbzo_+GM&9@ceO_S0l;n})kU0~)TyyUuQ@mC)O*+C zqovn%*BETAwqC7_0+ngb423C&3(L(VGg}j`1;mzw$5e!ah?g7uwpmCr-s8u6Pi+~y z7Gk`PxJw6_tb$9*UvaN2DWA87LfEMK6)9v?bKH;20|O}q5pgN(GCsdcavqLXh{J3r z2QUz_6ANeiHKEGb8JIFgGccAx%#To=3EzvAx-f_Aeeik(cq!bu)R&Bp1$@u;mB~;P zI_r@kjHiNYK)0GOQVy5<=b&0PiytU!2f#}ugjeNf3+g*eO48(ss3V#6|El>tQdj+J z(kJsZ{5-YwM4u;_tRKyVH$Y8$SO|$Gzr2bNyoxSQESkSl?UkN&ZPJUMNBGZG0O!x8 z%yzSOn&JYE$7yTEXqlA#`suR2{l{7Zn?=OVmYg$Vv)bg>!}Wxkl4s;rL~k{6UhzTs zMd8R#Da6ELOJ4N;tNI2NWyk%@B$w^JC(A*t;v?YUx30){_Vk0D}th}Lab9aW(43;b>;T-EBDHt$hb;34>* zQ|$IBCM({H45zBg8zC|~t>TW91cwbEK6d)((V^aKeqnm;b@#RpkRJM(n$k+IP#$>> zvQxf^dsER#cyau(H$uM+Z3eaX{c_D}*pI8e5IPpF>sTyiA$gEpp0;Fc42766Kh2Qm?i!ud!oKKq@|gUpa@6e)CdHhkGr$Q27W6B3bh4Bw_{ zOlB{2JUBV|UQYRXjhL6S6QpmiAZA#r`hi@^re1W0gJ%RNI{h^n+ahwBn7m#19rwL) z^iAR}@{DUhc#E~`tomN?7yi+maTl#H-xr07=-Sp_f`8%T3yc>9%K8VJuH23|QIo=h`wQ}o3qZ+ZdQ~Q7(zBrGH*fyQ%-6ad zkZDi}^T5uk`~1=LUygk8Px|xkDjpTGH#yI8PzkBrNkzY`O6fP=oJzNN3nXuv$%7lK zC28`nl|jl^F~YK>vK5=S&J^!>a?SJnOw4j(D>iBDrI6D29~fW#zoexcg>jpSJ|uY( z3HKZLMrVdaS%}snlsvST@={PxBZEW;Z!M)_MwQT2>jR?$-B!m!h8d@AM|Ud%(o@W4 zb@zcSy1SBvWk~tPwT<8ToGkpXA1{a58)j*k4VYa!TduQf(+vnj$)5q(kbSD72R&Q5 z>hBN=arEI=$Iol-Fk?0i1(RrEde|n8sUEipwW+sKuIXGcmhCPYztE`soiDkvjMNrj zV`e}iH!3Q`_`dUNgvy3G5<3Qu{W0H<%LX1ohuQ8`dD-(AKOl-UOfI_gV@8C{xhlba zbN2dsDIul%Q0|<&z_ofwHG==7Q=<#zDYiCdy~pXs9=QiHM!hgvqc%$>1KsrNx=)s$ zNNRdnSBQC9!lFKEu;SVJ_Zc-2h-1n2oqkdaZ~L_XG^9LO5|vQf-X>dn_Dy)1faz-$ z{Sq_IeEJX*e4{tp6k7p-luoA3Mht}Mc3 z2{E$eW+gb6PtXWtDhDZz@kkr|W%pPLeQgzW5dUixI({|oN!&M$fRsWfknMyQN)@j6 zrM1a^lB^6JYjtZt#8D-<+!O-~iOb$yG1+>bkA327-Kh=ftnkX`{8oYR7&=IOf4p`k z%AW;2y*Kjy`sHAz60F_4G*mFd<1-HI*@qTW8H&i8fW_R#Tp3{d7X4B0Gkh~&`N&dHJ8Tca? zU+%)@bbimQPW*dm1*zlm{H=12(PF0LdHY^Rm1xictp>ABd1ZmJBaK16s(hSu9S~Nphkgk;@GL)EAu(x_Q~)QOX8)I&|ypE`eKh+PhuVvspJ&fUW|Vd&`a32!IK zn}KB|u+N_5zt+5u(>3mqPs-A=6V$0we1#|RIDi7y5*jZhtyQr;la~Wc{N|&jsjpiA z0FlbGp(gfXaQVv>pT$iNG$)?L+46Hax~ywLa8mH*4J`{#a}zmH>w+_+m7SMr54vz0>-F(_Vf z_2GfdB0hU5-oLTU$5Y!j{AyP@$Jh6)tbShjK) zt7fPITVStkn${j!TN}JJd(|uEgq^0mD~&> zPa3`2NM%NQQLOF4@Z@-ebGUc0Z_odQWz_cRwZ5ws8J%(D^Ig?j7m#*h7|E z(i|qzPmgL@GGCCA|5}9k{>k- zOml)-WnoYHF5D4PYM#cxz8=4rbs{^UfUSIcXSwk7GP62?u}^tk_*wP9eBnSMp)X@9 zcDK>l>{WYNLQ8a0gS(xT@ABSEU7>WQURA}jF*!MRT4ZysAOO7LAR8O&GgngvRz~OL zBz$ytxDa+{aD8+_C}b(9JYz{CxKnVFn7idh?l=Hh3)P)HxFdjh9=t$pc=MYpMfYeoA6J&^zocw))ssc2jH(wo|}{ z9V-3Kmm4L>xr$(z55-8*xP9X9Ghvf!;ik_+2~&hc_U?d4#6x@4GvsoqQ$-3eSAX(W z-TpEcHDlu(Q1Lro-`K65=R#wm#=Urz;O>rAY?J*iPuxfDY@hO|O%y>z$hXnD7O?^G zOMvOQy!&DwY_qgA2s;y`+mMW(F~f%~j977%aPu}0mO9WX&X47w_&83iGC)v&XuXT; z76ZGg8r7WZ{+cUg+nB4kJUky&HxU3}m=GErc=CnO)n0=7UvKwx)_qZ+)Vqw4)-{9Q zjJTHE#tsx!VKmb#P_#ZFAhyu4eJOq(D}uHng*DW)PWtZd=53W>z%r@YqxR2Vn7#*6 zdkSWCaIiUYdD^dx@-W7T<#rks#8K4}VPVc^jIf}F&I+0p>4H_gRaf3cz|KyKzC;XS zYCGrpBP{A9E$Ul+DeRv0mCz^TbXHX8gha;WG!yg9&e`lLjnw`O^$>riuQ!=o-UMIu z^MzLx&DiL-IjCsR3Or(}AHvR_CfpqVfTNqT`|`*ppmv&U1~f)a2)pP!=2D2jX9rx3_AxwT-zF@XjMCeOMVr5bwX-;kN?`RLNXPDOk8$uFI|mcrQR zY<+a6JoN6qZMFPBXlu&T*Z{w$8j>;e$xQFnu2U_mm#`k@*{AqK-1$!Z*}#0`IKCn^ zs-?c9B4`wPGa~~Mk5>e!KZ@1^8F`+1SkjPQg$frGxM#f*jnb{L#%Y1Agg=xcHx_sb zM9`|<-Cb#q<8qBmfQoNDX8s}Wd^fbOj^3tv76eLN`1uw-0iuUkBieNWsO=T zUj@RG{X9~Q?T+_ONG-H~Ij1umuDwsWUT0W<@J4kzNeV^GxioCN5`v1Xy1oTpT30S_ zxnj=8`i1Ym7UBG#v)=#qx+8*jFdis>jzGT&oN19So1eH(qv>RdZsLs8rNbKy&B<23 zMQ7`llAo30?CohP%At~C3isb(?T$cU@p|fov;l*V%#($gza0GsNchI^j34nw$JwwD zE&Y5YXz-5Pttk!^6AFzWo!_Zzh1!Y?kGJ9&NpJKfvy5BR^(PgxW>0rQ;5atGT%3!kAWSl^5*6{L$kKLBFJ}- z;=g#VB&OuDG_;#(RHZ%qh`z&Y=rb_F6ArZCa%G!(>K@}$(thA z5KjK2@_6NU0iV;77${GupgOq#8swnml|~!&H564=K86`HGz42YuEu6)wd}pK5+5G} zZj@xkM3E_vsJeNVo~<3;G9#|}-QrviWQJ(Nv@P@fkxYM=)=sZO7C@+r7dvS$zS7i|bAlEy=i=QO6CA z@TE(H^tAl%YnE$!7&L}S_<_7&-^-+o)~H%8LL%=gCt#%3Wo1{ajgV1$I>+6-G^6lY zn;xyZBweh+OqwcLxG^9bArv+VEu}j8+f+T652r)mZ>veElg2BB;u0PG)use7?7N6# zbG&FZ10VMB)Q>^&8L7Pew+Xe;=e7gINlu*}5eJC1dW4?^D`rX}HQPvOG);Gf6zNDxC9-YE7lzCQ?S_T z0L+={iL8N&gkoytMILa8^=ArICe{pvXSC!Dhu?A8E=8{|`M6$wM3{Cc9Yn=Cka1Dfp*N zE33&qtG>9Z+4K3^YSqanS=J@#0Lknv)jW0aeldHN)YCk@_vmtW2a9vkTEiY`u1bvBt)ImyA~d4k2wGj+xN6yt5DGo*Xgvb>EVAIlb1`bG}$5m0H@Lt{2F_f zTg?tTF}|gw2NfQrF^*mTAQ4AKwgIYU6!5zj-;2cpopBO{QYl7p-G?3AWN;7F#Dodi zcOg40jYZiQ=!mD7uxl;JBw>r97H6Jf&!^OyOSvsmhNpTaafaIMnl`<|4W|ONaF69# zGR-$`ihC@w$K)fB^yzPpb?NSIwtrfa@-B@NABhH+7v+-)JN+H(y~(#LE729UX+oXt z`H%w{vRcnvyZg#7uo>i_YY(m z1%0G^u^591395-~AI82oa$s$OAAHQ=p|Y6)biGt=ACEP>66E2eYU#mgPWc0soxf1+ z5_4j3uPl1vRRFra%pil7ay#i(w*dyM8yfWPRc2K5cCp&6qi+g^oBThbVCZ~CNXF!G zxto-nQLB*)A3a3p@~Z+bo{HWM_Kho@+9}Fy8z=oxnvwBdtlMDGBij^DMZ5*-3F-oO z@wlIqk1xax9eTC;8t_-7mNibJ#V7kqn}4dUHPUmQ0tO9As6_7m%=IgzOjD=AAiK(h z#y239T|+>d7FJ$Q=;Q_!i4U^Xwe7Gb*&H3B<)OWxWecKr7cU(?^{t&&x-|0r7|dRY zfZ9_5^(1iTa=1!6`7Y02Rns?fnY)iosr`Nm?jMxhsfgwj6do@PoKJHF^u8-Qs?38Q zM~@&pT76?iOK8KxZ!8SA%`EIEN}{RQCB43;#2v`o?kaRmW-p>XCYup})7pEj=$#iC z^-#MqH55dDy+&Bg@4l1(5%STnQeG27!&h;8)z(l1$9{ZLwt>7f3*4q`fiIEhxXs$c z{MXnP>z3yD!?L|NQh?ZS#+cjfXZokbom<7QpCrccWU}5wC=S7F5hRn7I<}3~+eF1> z19_{gkTIkS_==6e9r$j!s8qHmDF3j zyk5r@wEZw5@H{hTd7$a1>R9un2Y8^KKHcC>NJ%BNbJBIXIFxQV#-P(Af*Zqi^9my1 zMVHCDYbw^Sew4?IzZa(^ z<2hmhijCh@$;d`fuUehaN`rf5Xsd*zxI{eca;X-}{r*hX-;ktk+>9h{O4nQ1@-zr$ zfrO+B#n*oM2t45bgRSMmxaMi4ES|HXcaZBmV53gZDg2uCMQ4CImHq>@xi%25mZ1E^IpG}TKes^>hViQJlM-MBPhU@`nHq~{Dq`|O?k z(wZ@OX=ycNMTD08>edGp5yK?hAMv+EeBbB%`Cg9vc+-tim0aMit*r_!Mc!WjSmw0! z!*1c4Ql4<%rJrp6Qj}bAhexPK^p8x1WA`OazjWzm`1`^RSfQkuqj z7u*|-c;j<%vNG+AvT=;7=P`u!$I<= zn$Ryw>lfE~oTEpbS4wPTYL}*31wXkrGF=&YwL|#Q@YO&-BJa!($ud6pAZm5I;?cB) zvyI!Gf@b~n_e=0fv#!q`QIul$gOm8b_7MCiB%FV|&MRM$g+MY-c?~z@2H+C&xAn-6 zu)$Y{1inRCA3{_LN8j-mi{prl8@;xUoGb?2*> z#y|Jb56>f9;3_UuC1l*x5bTeE_VB`-Pv3X;r$dS{rEZDsF*Rhhv)xQ`svpe5o zVZ#`ysa%|BAz~{7;lJ~p60mtD5CIO1^YBmgLlhW)n)gpN9)OCseu#_96@AjDQz!L} zL5Oq8<``=KonNkuPkNPK=4mW;4gVCoGdiesqOuVNW3PYS6}b2@I#)jR!o>aFeY;Rl zPytNR$$Iw*?Xfv7O!!d#H8lWV|7L2vA%gX0v~}pEhJ4y-VwFj+QZ3WOqlqS{8@ud3 znh#_)pDq$hW<1X=!%aTo!Bt-~*1R!Lk@!uQwiXq2n_1yP))k~?K#!Iwy$nvVp}Lx; zI_%_@`ujs|JEoCzuF1>c=l<_+t>iCA5*E;L%4iREZ#Zpj3I_#|9^yB4$=+#awqCUG zF7v`sMeHcg!^QgEmraQ&X&G45*1w&eIt}~mdJ2F@E7rBu>k_#5ByfoPZg``Sriy)s zvj6aC)giDvp_RTBxg@<@|Ktd|bV8d_MQnL# zc((3&^msu1$eu+2H9Ljvm{V)RV2IBMfvF5L;C$nQ;K^SjZJlOs{;}@<^9QAb6BGir z2!79J_+151bFtm$a=6!u73S*_=%^h6>e5AnrHWvnlt*NPQ_LJ&{*02w6mg&WMmw&7 zfI0{fm^HOMqIPAR(>x}P5BIF*r7-D0N+H9(bwYgnb$oocrFc*GUsKEmA4>c!w6S2E z{)%4C=3z1Za!daS&ECKDmVU$tkr7Vr%VfqzQR3d~HR0cguCEeALLL4QCG24G^`6iw9 zr&?)Ws#a0$dl0Ieerpg@9vM*Bx!@uV7@-BERujKKpB+7|;KGCy_@Xoz1?2h1XP(>7<>Y7p~EsE@Vs$oP9l-E$r&e;3egR{R?2O8@iSHe z00B)j_H=Z(j7&;m4o}YYrJzvFY1|3cUMNX%o(4v7bX+A>=fy!IZ$n;YK?a}`?(>S> z{G)D#E!*57gR8a7VPB;Ll`7`2Y304A*9uF1?Jc+{OL|Kast)F5Cbw@a$!q5hjYO6^ zMIjDviziV>lAW!*`Ot|xcQ@Ej0*EH zeEQbz5CnXx-b*3Z%hnTbEe@ zbHT=UG>ZCXHSXOuuwM+fGD_U=ikhHJ7Y^$`zc4l*8u7UxHeeWiiE+{E`XmQqTKpzj zcm$Pa>1SP!{TO=Ip_DvoM#tGce^m8}6%xP%utWx|8)7PZBl<#0Dr{4r(&X3lT3XU< z_`crmEs}3#!^;zygb{X$%?LO8v~?KFN>zJ5U02#06MFwGHfQ!#5}>}a65_|UU57e4kxQ@Uv-W4y{z%$> zP3M0Oh52%bF6pZRw_kJXV_?Zr6}g}6p4y(Z{s^C}G^&elu8n@}Y~4)(%7%_a*&By! zxlnDNOMTxhZpbc<%!{yx({o4YHQ#9shNTAPZc0tfV?~ z7CN3tw)-tQ9d)kJZYKck$~iV|0RRCC`srVs=`_fvp;J7$Ed{=RLOc`rz60UL_B>!v z>Zc!!oNYj;!s8W7Q+H#c%M1-1D^U%Xe&^fw9lNXS1=5ud!8RGY8e2~UvClHYM*G&4 zf|-rF%QEQ^if`vWaa~{PNT<+3$AETcDHWR7R*nW*TM+|KXIM|SGziB!igvzEahDei#@&?P z6>b6lfjdiFdPlD-ngicLR4CU#hYbn{ZSZ$tI=ZFv`vaL}NS{3nD%nt5$!CI14XfN|7!TY|CW+$$U?+mFCv2tX#c&I1CXKbTSq&+Ex8vXc zOanWw*AYBOW$98pT>5@NVI`6$*^DPfoX$l; z!MqeYmCebA(lqBz3+04$TM8;Ve8n(MSwckIq|3?~ve#UcZUU7>dt}2Sb}1rpx~k{7 zP!L-6_-=je&wmI4OR0<#*oqAZLin%OO#(9QSH(w%-k%>u>sbNSQQhxNT9))c_PRN_ zF!Oiq(xUlJF6d44EBE^7dsZM8H~GFu#0YY>GTolJzv2Tf;N1L7tdqA6Im2R*e=fkzPxW3ZpVm^pk6zG;765hqgy^dl9?x z6WV#NcRlA`);SumM>6_5F$eQ<0+^w6ooEYR{30^3)7W{;aQd7ZPtdLceG>s%7o%r- z5gWT@AuzOw!0q7vB5}c;YUi=oWty#o?ghenneYZD&Z0i$u^&33z9#@sQSZHQ6;mT&~{{&aXU{eZOh?T-g93qsP2D1=(pc{;3`scyFr? zH)pk;>0jgDJm|Zu-NPtWs^`fg;$~@Xx9HLRO zwe$Z$J1aD-_J>Orm^{IlGV1S#f_)CB8udAbK1t``M1>^jB6Ik2DF6T$?p8=XaRi z%dH~s(!*Q^lyO2=}TtGuEb30&2O_aPxb;B2to9U@&`6*%eX)m7hBUq8;Rf)!f8 z`%D^bE;CZSrvMusv$5D54|qj%1vZCcjFjL0QKoFKhm1bFMfGuh>Pv~so{cTds~QZd zLrOwz?3Y23nrF?ry0uNUKojlwa@gP!yuEAab=N-&@@pSms`| zv0u8Jeq)g)QCm~M-oWFRcx;2<9&wSOe0?Ij5Wi5SZ9`dO3A3XJ&ew^f&3QmYsm`># z+Zgp~{UxnIvj8fn?es!u7Chg_#X<77kc3e>tUUZlz@QU)D2w7GY2z251crfRug*z> ze;OZheXLeF|A^L`R5(>_`LS2kCr`%;v|0(SgCn#b=8;dLd^wot=}SLmJ9PCd{_19m zp*>GFdoN8B3{C*wRVdVv~gn>Nk`~C_DBFc6erA} z=B*qu#_Y=QwG&!4gc&*e5r#~<(*Tu}trd}pEl=xcN*nrkSgj!7T~C3&H$x$IAK#kc zQVj{~g!RB`f6A>CC&g0p{cag#hv|Da$T0rWfi~x4mu6q#$zH%U`6^gm>LG!#8M@{_1 zJb=C(K-Kw(TSv-YJr7yES{*duQXBpnSPfu@G$?J5*)}S~j2^4ggIaC4kvfc=sq`$o zD^M>+r)d^+Ywslht}XrL#SxQV{@HD`xQ*oQ*?Jdw$%UKsHrMOo3To%e?+U@RkYR%q z$6?#lPBWD~06?A041fZzs!9n2J>zy&U0In%8V175ZjOvG=ZqH!s@j~K{LZ7&WU1J9 z$y79`e$wGdKxr{y=aiedQdD;LfYK0mQH3i5i@;D=#owj5Eo(w~1R=^GekW zvwtUJ#fQ7;?w;4sc3|DT`Ec~&Q+74s+Cn|CbISS}$r&li+D7k4A25EsDY z^XdO*JM^ES^2j&knx2hT&Z@A}!WP3SbvxLs^uwOk$Exv#NsnoFp4stTGkW{Yo5;6( z{8GiEw)JnCACR$1xO4QV8mqM9cX;}WpdV*g<)5MWow^U6R|GxlESLVV>{U6I-Nb2O zsG4>+CL^)a{PX(>m-$7@X4+iHs$-{6+$(7j$NZx6m|SE1KOfDV;yd!MkII>Q|3rp+ zb3XI=%I|!2*Cq|?4v=P3q4^?_5n+esd{;34`j!4GwH}Xv>1(rmS=mvFBh8PDW1V{G zd5;BLByEvecRcMoV7uTFv~7{o6U%TK%f3(=h_MtAQDQNGnV$=IkJ+22q=6=n$xA3fx>&Lo zGUFI9d})A#n+SEBWAdpkGet!$RH%<;^iCy|7X@Uq09oY(Y#@u|P(W2THeZl_t#;or z)I=RC?o;ZPZrRM)v^z0Uoy{6NtgALWYDU(4$sA_ z4sg~w^#TSVO!z^AQk*PK#nh~2ksvm6M5H}r)3$8YMQ-G}|3RZ=Nhy)uyAZO#%v#+~ zZ3EJKI-!CsATd&cHTir}ezi}-`=9vEMi`&?c0n@Oi`3+#?PgQ%KbGI{K{kXAMD|72rO~6)Esk zq6M4;qyfM$lG`CBz4P(xLS9(C3&V?@{c=65!g8g7PWU;61hQR})xr8J++PMDNw=o? z6Q|a{Dx*}s@1g6l+w$0j4VIPHg8jE&toJsH=G8#$uVIzslct3L0KiqNgm%&+stU1P76Fq=XG8%ihv7Sn@r9cqvy`;KnZhve4Ip!@fmUA09b%5VY>lRdI6C?-_gtTo( zjqWtlO&xB%bilgPz3$sY%LWGE$r36dD3ieFBu^}d?tl{MA#J$K%HAg#k%MPUIAD>?P|*>~Ek zY|wbzTE9YD;`V6v#rdP}-~`e`FK-_US)4AR>SeDGe~ z0i)`UFNXtFK%huG;Uwd!-=Qr-v(@I{TnsG@Jks}V@NyRS2jc8MGs)TPlA*MHC|PHU2x}KUNf+r>_ds<{F&5~ z@QHj75{5K-?@crYWH@fJ=1@84*P zzA+&wckr+{BezdkGVD7?GV$G8lB{}TyGLz`i-ZV99g7rD*EU@{*GCu*mnHXg01&r^ zK%WtX;zD%DW!saPCNF^?-AMGSuNpZ0-_{sUcZ??I(~!%O*QcB<3q(nmPe00!IoZNN z0uwV7I{QQL1}BrUZffJ-b>@w^2_LdHZ0yPcpkf!{z(`mOLePUeU&tnkyeOp2&qg{K z&%Zi8SDit-Ro#dl-nEg8C(IK31+}15{i^8wF>Hj{ zOxCGO6N}c7)|2IaidO83>Xf>u6#xB?x%F|u9S4& zw&k{KR_yuloq?h7@uh1J2&1l2RH5@3e95HQp?i)*wEdnM>7~0rJK*o05$H^@Qqy^X%1Phw_5Pm ztnCJ4(lb~Tl&oow{X6F+t=#=VBzb<(RZ!3M$)%m}w776$Ms&=2s763t7jfdk)K=bY zNFO*JuMExwYPrflfTi|)8~mX&>p4=A@d&5 zrwS$%QUD@(9-&R0E-afR-dTECYX5H%^~)$=gMMu%|3Yd}o2XnU*dS$dvZYP7FRvIb zG~6G}5?cCh;);Gi3E_*fg9u2dNHMoDGATDFElb^HSo@*xgjXuDje~5N@=5PJ{Zl8oSf{=dn-(Nkz?-7)8!w{cq-?{bGZl3<4Hjk z3)gB6%QSwdZtZjumzWUDWOHRvdX#KB|G6y_u1kAyqY;R1IBIZ-DU8tigsOsq)P8&U zf7pBPxTdmwZ-`ZJgueHAA zS*iP8F&bjJsKBEpwnZEqp4mDRn3*UEAbA5}K`HWDQ#$upQKBilN$3<%kfrI|$qkAn zZ%e~lP^~*9&Fl3YcOgJ#hc7v^0jt{LPB(4LT5pWdId#HcJ_>Z*H3-9}DL zAL16rnhF8_JRUsk%sN%VQ$ZY)Ev>v#AUzBnfc{q>p?od*B}>Q|4v~? z5}glO`v%zfjnjI+bfyUAr2|L z(31o1N@M2tuqnlxy@x?wKDUB+Bl1A4N;z3kp(yz^|2MylgYipVB z@Ucl3(~Vvt&(KZC7-)=`n1_V52|L0Nu~KF;;V_X;$xqHaGGBt_%crc^PKgVpwEDk- zgmM&aBTT5+sj9wfP-A4`mNM8%J%g7c?4o+IRB6kUZL^L0@i8A0m$*~5ZT}Rh&+;6% zZ``rGOk)exLvw2x9ixxnIbZw-0=_jU_RSau;6G0~@g9 zfV7aB0=-6*l|C@>uE{G9L>KO7#qm?3ImqDw%S0bln@T(>A3&UqIV%-32$&IRM^mRa zW)$l{j#SC-hTmNq7IN>S<)T;(XUI}5lbDRlh%$_6ge4ludrpXi!}F50{1W|c=H3-n zAB72N+uQcKRxDAI-^vK09vaYOZ7S9rPqbOuV_Mx5MhNz@GPB0tu@56G=w2~%B;}}S zbx~1}vMkZ{CaD7jwrr-lqF*^QY?-Lno8WdcXcJqH=L12rBP70 zI%FLKMLl6NTBU5%-tV5WFqmlRuPG;75?UCOuVo&7rs;1x&cN`oGu?4pMTHX+v2qPK zhC{@Nuv_hfWj~cLT%Fp=ff3fb6#i3= zXY+KI=nu5Wi;4OQXC<$8LsZ~3gE+Qe7zGnjqi=nJjn7#=NR2rQ@vnQEnj|bJ6RWST zFcDMgGTI<+!Bo~x7SeZEv*NV~4paI8F@w=SG-#o}3FMQDE zx&4oIau5^FxV_>f+rLuPZ$uU@U@zbdmM!~!5X;V~06qn+_4Bf^3}f1v&A^$$%lPNZ zhUSX1C`?6-96d#tlsurGPw(V6lu@ZEiMs=VbK)Rw~#JTwF(bSxB5ow%2q z3V1{1UyJ#_u==ra$S!!W7(oTfmc%TUoQ_HJgmM==KwJpWW3U5orKj?>-1J(KJ%c_x zq!@((*dxtFAwSHU^YX7n$$H1SzPbfY$OtG+Ab}pJm*grJ3|CKAQ?SW{{&u9f1Swmo z(#!%78hT{A#)bx;{6JLX>R?upX`dn}fF|@-Rn-MvYRyo) z5su+28$lr52UQv$ln!mBPUYTArs8Mp4QKQZDz?=tTUOjgT=0kc>B5UF+u1P9_ z<)Dn}{YQxY;cn5p9P8@v&Uc^ZJcVWD5?kK43(=&ieWbYAkXQyZwE_ ztknmJTd%V&lQuV0U(GFZTk`N8$>aY1(#r39c_Lv^vMm3?*hQ7A|G#dqK?=A499dG=d$G%og4qxpx+(^q$M-^~5B zaHmnq=>q7aP{#ScshEcyGdU_{M4P(g%*NWzM%ce0>5FHbx9El*3J|uKprfqJ0-Mnr|*a~f;dU*FUyEu5!eYhLF!WUc+f0K5~6Xvg!e-ut72IzDo!+x21x-z};)0nfZ+m0o6BwWp3^A8@#+DkHV(S<`g7b$!z`DB9_TLXxIw+ru zOCt?xGcyOE`Vc3i7vSyr(?2Xt?NV#WkR{Snf8Br^;b^d{>17AMdYHXsfZIXTsfdRY z-o_*`y~R6m6d!20))Bc7nx??r(mHF?U|igm=ta!8LuT_%M}k?6e1HK2SV0CRgyDX3UDR#rxY*$X3^#Y7|TjkQEv!nHt&QgWGcxk z6Kl8ZYtw)l20zpXrakLPOeEa~>09@S&)!l-&%`yNw4?y_8u7vy>^uCnOVtXdCJm;! znIS(6;oCY9q=*C4U>)E9u~JnT*lOg;czf=NK|f|oDs9jbLVvzAS4u&&fZ*&~5YI8?SmtCe^76=? z!Lf0ll6oe4B_O8cZoq6o=xJ4ex?romTjRvqtGcs-9z@$IURUE-YC*64pk4pO@|nAd zhh1iA#0~5B1^xGBHEs;+Tl-blfQ%!}k}K|&T02O-gXrAtB&WR2sNT9@JFJD`pExgp zH=pbO*H=K`436*L{dDTu+)saCnEW4VH~%d*i8Hx9^{2`0t$YyJ;B8LG=pC!I#q&Cv zM=KA2K_Zy&u!}?#K%$$Jg4}`hq#DP0yf1kL(X@nU8h!#d{`wi*_y?l>ThXsef~!Yh zYY_enKMiW~gOK^St81x`XdD-CCqYN!o05`GvKx}K^tJ~lEh8QJ3nzhVMLLH5`)tC_ z@j6M?JVw0X?pukjotmOaQMyOB{MBaKxtWl!7woJw2?J^HTM-EDR)LmY?*`Vf;~fqX z3*m_e`;_HN$P41pB6*9z#)k5?$=seCUF%Cf2g(xvS_KZ;r~0$8PdOICGL&|WePmxi z|KZg)PYF>#kIYe1WsDhe1>x`rP&He7^fn^$N#Taok6)8J|H!lUM+5bLUjEcT9oObL zwLehN|48AkuSkWwfa=$MCVTiDL&AsxVO8BZ>lEj$G*b4{Tj|@Qga&hyhaBG_4$k-a zqNRV|8^!fG(Z>3&fxEAMLsN`#-Tw7YK|Xp+nA;%;KF3p05uPtLC*|7=7*Ic4wtfTt ztj6w#uRmvhTsNxfzqX<|rt~<|;-}otInAN6H}}H(w@|G z$Jv%ft5rM&Z>3&+K?2*>Bcy$P2bU}gSGi%s{{GP2U;p{4|Iu zOD149ujQm>VDiwXXY|EipV50<>Y^40bQJ`~f^(v)5Ny>4)oql_-jVx>p5o1p(?oN# z5g8aI@#+U5)bn1u46~zoI4Y0eAg>Bsgo=~-x@T^y+Pl|lt zTRo*#e*Ms+?VW?5>b~0g{(Phg1n0_%P0XLBdb~C6sE#KlAI;*}*I2!z-Q>PK&YAWx z6BjM5$*A1n(=6fE;&jz=x!6L1L;eBcwh6c4ZWi6mpmM%d3O>YH{jL1!VqFGZVCA?M zBq(`Nx;(R9l)GPA?SoSB`w&gQBT;yv>OnCl3hF}##*QM*aW~V>hv5cH6PYT(6bG4T zB}=6oF_zxEJ?3$q#f?Z;_kz7r+2BF$5}J1rZ2Y^%F?sN4gx7L5%hOe#7J0KpN(u=* z##y00TJ$Z8^2>G8FcL%aYJ=tvtOUV40!oQzds$HVV0T$`YDZ?=EG4M zbg&D2c4BH-C`}{OP9zMBWK?MArm|fawu|B+mVLqemWs_jrrn&iDYDo3nyG8=ahMX$ z`Z^JFmF-jYc9~nwblch{d^Fep{t#5|s(aNq71nF%G=qYCQ!~(q_hiPCwbFVPD%Eyz zVx$eoi~P`)rD+S5X3LwSr$_AMnbA8%XUdz#)=s1p>v9BAU9uOnZMT~#!7Hg_a1Wv- z^bpeF0e#%5Vqcv|LyvkiYLpLIfcqk=FDpqv9#?dt@hG#EOvxhNTX9Owv5sZmds!{J zQR-h8Mx|{|?SeFB?5d%VaAKT3R)GKhAEcLuzj~QH4 zfwl`PG)5*JieFPTbhu_xVM#Y3?J}2^v!bA3{5`q7FXZQj6HzZkO>3~C$FRocFt4m; zC&D_|6Ua?X9V~m_b|wdl^K=|h&`5;^$y6;ZZh&~u_W#noM zho{>*)Pz-qvpHV2&t*SyC8Uv>8wpGg@S3_aLN9mv^-Rl;(-cm)fJm9fY2Ahm0*M4A!Fqic&+1AmI;yhkcS@)5&Es5d>xSb0l6e{G%`b~7IeX5JTj!%|^RS0Q*Z*m)}5`X;y2b$J{;Q&fRy7u2LU z=cL^q9v-4wLAlPe2KDKB+=JdAZHW;Z?byfKdc?hiFV%W&QySv5HG|i-nCr&EZxe+$ zx-eq}Te_xiA%1sF%75uPnkJTAfnpYAyd#%4PzUiUDAJaLdk!@-aduB_c`laJ-tQ7I zZ|2`SE^O)021v+wIvI&q22Ti;GVLYHP}m3E2f=c`00&6WGEbwis!s1s@KnE=uSZDh zmFceS^#|LQl-kHT+$U64H zOrY6yCF=DQGX3WI$dHx2eWPCy?a3lgDj^%L9ZwXGR#*Jh|o!?g1{6JDem_%W%qX-z79Q%_)r#vIjiC_Hv_|utAJ~0}+WXo@O^~wWeYq z(h$S%83~sC1jpoQWY`rtf&^$p@gKh|fqjQ5Pj|#qs;nfUX0h{;*u?Z>L}hjX1f6Xz z{Ul*4^`@AcQko#9D z^=hLwlfgx0H2J|5>6x@SeEqoCgJMio*MJJbMsI8`8Q3G9*W}PZ?1~9@@(!mC7Kd4_ zdVn2~cD}=J?0tfs)*B@~Elbmz>RcD|gZfG;&0Xee51tovb+E1-8ARr0rVKUcZBff> zvRw+6dhK7t9TR9)&F<<|zKeMBa`vtX2_I>6$BK;6NyCpdFM4VVv1(R;);E2EiP96I zQKCj1waPey$(Onj4l)qy2r^k)PirvZ+QWe0iIWWW#1CaY`6-qTR{Nff480F24rWV( zqP*wgiH~^Gk9D-}!WFX>d}@ox4X@oMM(T~uvNN?Eh&Pe!Z^4~Q#n>ELb(s;yc>`fo zAK-DUZouk+0sgA%v}4D3^0O6{q-*w0I;qW`2pa=>@!F&pr|}tzX%}zK+eDpg-H=~M zpd;Mt{>|&(0nehA2O~-CZB2yW1cHihRVF-WeHO7mPR`jv3KAZp9#6xHm#Lt!l~g@^Cu8RS)WEKF4KhThkOTp!3w4W^J(auIZHdiv({&$80r z_$rRQ?FiR~cTDLb*4Aj@RWlo_+J_S3?S&7OoiJ=G4|Q8wdvt2ZK~B{1go(Ay=ySvD z58v*!ZE8X`yWZDe&a_C;?Pt7=QV{Yh6F)3FSaKa(ZUE_Sp@3RRQjR)-X!(rD_BTOw>evwVbi;ntU{xuYdf;Pg z8XucWO`svC^06>U%n+dXu);AX{U~P2Y*Omnp#(pC038QzC7bV$s}66#m1aWg{AYu? zzJ0hNtThU%EeSFuf{{h~>Z)$~9O!BnN>j7@c7N#Q6Q|MS7^58N31uGwnr9$EByk(J z1t^7yWovrUQG(p}!H9_Zwq~uy-G4$Ybu)!!y_UXh9G`!x8Lpq_|BH z>9h^WhzbI~5_^T=38;HfQ;GtNk5<{$lsx40c&8h@sms~o2(Wd10{3k0(HV_zpa8IH z%&e;Btt42|ZAG8{;BtITsdpOLe9~U)p=JOz&b{ z%`Kcxs>9WRx zPbB6{2XKU!Q|&fFU#pb6M`nH62NdEUuNyTZiiUy6^{g=dXr)KsRT5wxQr7Pu3+GRC znXU2MmD!tc6NXIq6c%`2kE~q=paNY>2dh^u(iz;K0aYh!VeHO&B*fr7pDsuja=EDh z)htgP#AIBty;=Q|8Qx;AZOV<;Gt^D%L{S4>Cb%aF@HFQdpig zY=5&P;$n~CzjO$~V^5jA== zwqu9MgGQ}T9+D%Vs+8?r0XF=>pLai#x1$EW*9Z zG9WhB{d|h)N{(|#XPYMnN#OFfKFAQ z24XvD{kuI09ntB~&QY8=GudRZ)nr5UC$*OeX|<NpjGqJs)HxL;q^1hSsh!#J-h&#lrj9}m zRhF%JlM3|uG2H;YCC41yKnO>R$+BniMqu+@k3;)9P6BxwN`mah-pwqu=sDRJCLo(! zQ+xf+y(kN*M>PC_lI2iaRXrDEn9ztS#i$LhhUeC6lqsdZe=pA03#m4j3 z&9pYpI95S`P;d8Kdya0P%FLw~sz99VeSCPHd9W3l`F zCo(=!cZ0srmA=rGzR;Dv(3QT>mA=rG{(gS;Ka{SdZLD;GSm8SuL`OZQ=uS@CGF;M6 zi+OCxbU--XQ(G_cJ`KGkb`2sG?ak`yBR%`{NRIhbpYFcZT%r`3C;Z;0ffdg6!}q_% z^K^AM4j)&T8ze{6!wYAvk))E;tRqm>WgNZ|uMDPrzlC`6*nDi2xqNcMqH@4uh7gM;YLtzN}jj=U>j9VXtW3 z&wH5E-;G}hBk9k*~pw^$`V+XFT0^>QD{Yy83e9-x8Lv^a0o1k6eqUQ~Ty` zl|FHe{z5VOe869iJoqOjRO4RZYmeV3MxPTu{G3^Ef)RjSXWi;9y!DaG;B&(8*dbV` z5~K(Di4P^ou`1sef0Xm+&%`LH-0nf#UFi6{dYN$q~fw~v!i?nB5pPe;TlVU45*6u+K5{(yn;kmE?Bai zPSZ548pu~0pLm)o2rE*F`4R`JycYd12tGdACV`x-4pc-lm~rvvKydod8D z+enAJ%TT+@{qpe}gTX7kgnX#Izkzu1{e&e3HVl?H;#+=*!$%6(0+Jpnj5AReceryA zOqi`Jk%%3JideLE1ilP!-PEJ;CUSJD#4Z>y4`I@l?{6w&Y!cS!HQ@~!q<8i=pig$w z1ohTCP|dJ%kc~^Cc=~ZVqDag$d?mJLh?$grNt_Td6*qx!usp4*r3FL=G>5&ohHNH| zRl8}oVv#gHRl@Q`LR?x1VAp_QBfha=*?ZfB#Kukn_K+ycsHaAdWpRxLPDq3tsqF3`Ci;;psw1xRu2srhtr0s*pe9avdlOz7y=dp zXcTy!=Y3o3djdE?oKc5#)4!iA8=X?E5-o&Ns?!Fa7e5Hb5=sd!4=|@oNFxA_g{pcz zY}94ZvH#+jdr}-@6HQG_q~bqv>5FP3A)D*^GWDHeB+mLfe~6{^+~rewRaTH|PR9Ho ztqQvJ)AlrVmjLw?1m5xq@v)_Kb=w}<>~XRD$b!9HbhS=$XD<@l!(C%uXd7htQo>=&D8}-$V;geX6 z+}M&zfcTawCc3G84+;j)RZ)KE}5|4G}%n_(#Z{-wkoeOy8VY4#)@Dpb3U-$3u z;v`r2DUm;LfnVJV)BI5QPHDet;%Z5IRF{rOK*;{pZI5>zHMfl8-z5h+ub*5i^yE5p z^8O#F^X|EAgD_jwZhhwl{tMVh)%=Pm8@50I8vg3-1vAC`sR!Rc(! z2I^%^jI&ol5@9*W*ITqhuw!z0zs-@Q^fK%GG&9AoF~*1bH{7JhQ+knUbMGcB*986K z-TsEmbOE_qGeNz7zX>wr=s~z|GW-Jz*5a%>)7>9v-$;6|-Q7gY04c@`D)yxQr3xX5 z$l*}F8rItq$Kte11N*Idw(eyvc+Z_-n=tR7VMFjjOHIkdYqE}vU$$Q9(}a&){E}RN zrkj8G_Wx54_`>4*C&T^i&WB8zjbnnWzh1;g8 z^cT)g#v~!YxpQd}J$*}6as?7;#(Mka?-15pq9l6<2r}eB?0_#OL-zx3#q?I!%a2@L zzST*s-j|*khBW%Wy}B{M7G0Z+Hh&-d&Y({OKisN(+TqW6{fOH09I&ov%OF-=PD}in z#5DWQ$A3QZY*E^m1HMG*KjYEpvo-{Z??|GbhPX$Vo$mSRpZr1nzbW>G_Vmxi{ouJ0 zj2NlH*K)I+Ghd(5^OX%tG%+ZUW6Tw_5BMBy6}FJDV%TY1tY}{=$eT5o*ZtFMoeg)h zUgUI#H+U;uKhM=o6fb^TQy|dU-UiFoRsTdS`E&_iOXgbJt;lZ)kRoU&c3{Elrq@M{ zMYRm)0(nAGbuuJ=`sc3mZ%f@~P>^1gJ;WKk1u}FwK0*L3DIat;{cK_giRn*iABw}ifslf41qH`4PG45HV7ZG^cD^y80*Ea#RaqQy*;F`n zwm7qOyA(px)(k3W$xJ?u#?47kNf8d_1~^ihN=2`n<{l%dB~LIB1t8R9#q2Tdu5kVE zE7z&3S24LVFX&saTOLBS78ah^J**7HO)KbWvg-Hq`ehMA>qd2^>=C>re|@JP%40qU z>2Prh>GQ0ia`FDz#v$OvpjVY@uL+}M=b!{}W*4cVUvI4ly9&U$g-_-_GNP1!m_QwV zooJKU^^q%3{?_>jRN;VuC&IT9TQ$-&fj*R0@A(c`&=)WQOZ>=noJ5eTiS|}9ql4^T zWaBj+E4&nhDa%qqNl_h3S|Y8IDme%d>unYj&wE^=cpYG#E=EX*3uQ{L0 z{J?W%F)hy>ipzy8+1_j8VS3`rLQ5lh#w}gy$>^s@j7U<45SbC{r2RT=o=1KtOc}w- zjsA(j$240$?)-p`;rZd7VL~=heAZCFb}pDz0V6Ykf!L9HOD%ri8>38>=bH`_sme?tq3-)5LBTL%kWGHn zMGC0|n{&gfS3D$A04N@?PURKR*cC{01`MfzHXTvts&jP%a>v;aVB$Ox$*PI0&ZwEN zK2zB%PCB!GbRf{SQ%s?!JOC#qg_sdk^D6buus3c*9OpL0&X=Gdv8zV!B&N>x(^5>j zWPM!n#>Inml%VAcHl!E(`qjvDPt{FVx7P`*iw|g5J@$oQFJoVWOsE9+m3bT7ngTq2 zYW_vy_Tt&@$!D9}{gzq}fFDQ?N8q_QyHaA%dWc+LuP&aq zRIc;-$YlY2?MGXYvR}_!f#>Q+*QBK}rzT=Wsru?fEtA%nRgj_Z3zC>-1Q}p2D;QpD z$lj7?vL=dwDk&{X5sc{q7}?l|d@vQNqEb}s;&dWK@5#un5r;9Ru{S7i*<+HxerMQZ z%3ox_y5yy7#kd|wM?LJ|=jMHZWLP@1klwFJb%3T8&lb>hJ0mo3<39Dwk-LOSwlUmW za>sj-%6FGUG$I)tDlZPjyg3%>DdD)79N%J}ocngfdb z0r+ziw%J+mbz-$?Ez(rG$5+o&COW^#yp**pBdX0O{X_GRO$C3I?oLm#iwgu^l-B|G zL#D`hyErZHdv|v>)XdRD-H5>{cIGFtM1lR?{M;8Q1v+6>R+TDUBK}lMk@;CR!%T~J z=pB;N+TuM~E*iKuu-@oG>_i~mtq1Jr=9;FmrW(c@(-@{ucs3L-UVEugjFsO~lLViv zj>d4Pd8nxBHjy1Y*zsC2Ar8ZM1OYVAn5 z*8^#WW@9T$ESu1oAVs}JF>&THjFda&V8>EeNMdsTl3p_wT<*i1GUG6?Bo_M=5az zlLeEkAie2okj`+-F_6ML$=BPGPlFTes$s{NZ#OY3##WVjB~rn2u2u{^Vn!;*-neQh zSTKRNTD$gI5fD{t08`!{wq>#QJfMohh=rV|*1!p}@=(uqr3%MT9O5W%68zwDH8*CW zCPm^nnYtG53YO9ygwuD$a*Y(-&0>H?nHg#l$2u%Da2g&8@xG?GO>M*f-vk!Pk|?M+%9F z%9PRw4B=^LM2bu5L|?e(NnMCaS5Nr(_50<#H3_lc#0t095_QwgHCM&i7&N#EP^`iEW{$8FD?OLjUfR&K?6b4Czz z3ZZGxh%~KS6~k^kC}%1@!2XQ5VUPP^w-i0-a%8vQC2}x)E?6X<@JJ6%hZhTjh(KVu z$&&XqirDb6yUkEoeH_Sae`-EaIZdy@Q)qI&4AgaytsqISnJQAD)o9=DDq35PO3tz< zWAO7mGf2Jm==7SxTA(G*T3c4&oER`59<{ZAA73kEx;NHcg|SRyS3;!%=Up87pG^;z zwL_ZupGF22rAs$Vjk*cyoF=6sCv+WH`2{PDW<#6arC{4qSgvV5A6Brwk!l7=My6Nv z1;{|OZkF8Zd|$9-h9A`w8mgUg`JslbZ)a3ax`VZ?x>J8vtRfoF9N#rpbt~?~JvR+g z?BBjNtK4USl+DgtCf0-4KIN`L=VT}i$r=rNq#CP^A)a1de%PW+UOa*PI59rQOs1ei zxvh;XP=`a$cQLkXqXfQ*(o<$)onQClbPbLn-|XJK(4FoRVp?fmo;rPrxJ>p=Nm3jv z$`vU~J)iPT4=EYsyqJPOoqe<-mlSJr=;F+1lhRW7-Lu zjQOBWgW)3a3(tmt=T%rAhyet&d5N`lj&|V=GlPMa68H+dxhJ2`W^I zp6JO3Q3$>BOUm+q-saec@}v|KXvg;M#v2c5aj!C)W=bHmqB}#K$MRd21$20lFiR@Q z!6?50o)@X87a65lUm_4kZbuUsHqB>U2~CdVv)6q~b?w~K0STB^F)=aHC-i}sn-NF} zJRi?k?U&;lR7(eg6a*h31A|6K+uAwCr*0gP5#Z7zVu0HTPkgS}p{J4j=iNQ$wCd82&;&lAX+?#ym zplk+orb8(=!mO~dw|%qgTI|o-rnZC1x=eS;jcDaYpqn3g#M>ZyRA!Kn;&`lX1t2WT z1f63jqqIN76(H2yBjy%7bf=NndC+Vk$G{_f_XGe>0$>qICfXt=ymX3=<#|ar13|N4 z3H*eYU~tdnZ$Qe)D(!Tn5E_BvaAM@XtHUB3R^iMla!^nWJivR8>rhwcxDX}6lI>Vi zuNe305Ly=@CU%4S`vb22iYhZ`y;4V^Y?T&C146kAoC2r8b6VebUHbiSb->w;cg%%d z=d>AD_zaS^g0LC2n-9EUWZJjbCpHfo@i))*KTRU0APhK$hW6)2`T+PH2+$;FM{j;TzecbHB`I~aqc^B zq7N6E^jYU@h?sXuC54ph$Zo&Ly|8rvkq>cQO4mQ$5Fnc%{xBs4n;u5UCnsMjP#(B( zC1L!^e;MxF#Q!UoFaHvs>C373!pw1e_0vAMIeD4o01+PVLTd}Bilm`#p@P@Hd7Bj6 zA1w397E`B>*3m?1*7WW zlk;7I>JTW+@P*9|Ozc;#*j$5m?7kX%xu}SVCZ_Gh9P3UrqVfE%2Dl$t@JqP5XLPHl zQ@idiMG;V%NH%y=TA@+IfOL~|>3P+nhkgrN=pCv&`K(WfV5Jc&zh7PthkuO=O}`YM zFiyXnA*=(_=8-IEw%L&vO8I)!sAAm|wu*=FN;e8g*^e%4;KNWVg>+%Q(Zo(eps1-V zX-{JWFRT^z;^itB{9TYn85r}?0VQ9H(Y@_0%^eeOMjNgcJE{Hd3#5GY`- zqe1`mIJ&p;NFE)6^syk#Xr!N(djG7}*cXPIY}-yQJWgocmr+Iu_N9ePi@ou!x;Tv+ z)ReF6BLdhp6DDA<0fRWq-E=^xc4 zH!_;R%+B;Imxja?b40H-dFp^zj4-NI$KN`NKyiVszK6dudzu?GoM38$lhW!d*DmVj zNU3RjXH*JA+ zL6FtQx4?64M5DMT?geDG2EFi{i)nacQ}OvU2qYr!VFd(E?ttnjKF?sWwVv9M%8c;D zrm3ywL+8#38sE|5gJs88`*XkePBG-Cf7nf_obkZO-=>GZb8j9PMh9*2K)NiogtaZ8b}XhL}2;CMfYS1af`e z)QUIfA@UF0XnACLO`_%4@`EV3gB%|bAKH_*{DhX1+qT(`I*0jK+Ps@vLQ=LS9IbT0 zu1@m#(`B$CJGZ6jxD>t3>Y(gEB~$IisI~atsZRc#Nw+RX6{q=&D$YM_+y63O9z03# zmLbWC0zAJnTUKxh6g-=rn~oy2NI0I?@n@QUi%oMxkUI8>!#tyhYC}R~&N-*InG38t zM%u9-e=^Gci?s96ID_xXn4{*1WA&bLArsy`RAoroF^#(U2p+*>q>*}vTIxHChQaY| z6k&yYv$+0Q*LAU8!xL1ryL0WasqGDp88*LJaZk390OcfYTyw&@1c$YodOEnw@=!Eb zo;r|z65|%4)?p$Eq4X>+sxRYTF%iA88<6tNW^N;EFiR66_e0N;l{6XWGNMNnc`8%Y zl=RrTY;}s}me#tXvK)H9roPM&A_K@dUBo{@SU+kVw{v25_l&B^(6V&(_Snl7ePj@& z1zx{HFA}iny1lSle!Df(lLnuSP}f$YgvP0IU` zodNWEUfw-wm>Z?Yn>%Qs4+#XtgbU*oje;Kot`@}Twcyn92xS2tM;1|+VNtqUEnC(Q zlF!KE<~g)6pQ3&r#Fsw*ZhTR!>!&<5`OuBMrsZgF

?-ifp%#XASGv$=ewpxhAa~ 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 literal 0 HcmV?d00001 diff --git a/docs/sections/resources/images/argocd/login.jpg b/docs/sections/resources/images/argocd/login.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f41022a32c18e0d77bc238b5c72b947f455416e0 GIT binary patch literal 286631 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