From 5326d74dfd054ea1dd10baeb60634de7cad43604 Mon Sep 17 00:00:00 2001 From: rongxin Date: Fri, 1 Aug 2025 13:36:13 +0800 Subject: [PATCH 01/13] feat: support kubernetes 1.18 --- .github/workflows/e2e-test-k8s.yml | 131 +++ .github/workflows/e2e-test.yml | 2 +- Makefile | 9 +- .../apisix.apache.org_apisixconsumers.yaml | 375 +++++++++ .../apisix.apache.org_apisixglobalrules.yaml | 139 ++++ ...apisix.apache.org_apisixpluginconfigs.yaml | 140 ++++ .../apisix.apache.org_apisixroutes.yaml | 519 ++++++++++++ .../apisix.apache.org_apisixtlses.yaml | 195 +++++ .../apisix.apache.org_apisixupstreams.yaml | 781 ++++++++++++++++++ .../apisix.apache.org_gatewayproxies.yaml | 168 ++++ config/rbac/role.yaml | 15 +- .../controller/apisixconsumer_controller.go | 30 +- .../controller/apisixglobalrule_controller.go | 61 +- .../apisixpluginconfig_controller.go | 3 + internal/controller/apisixroute_controller.go | 157 +++- internal/controller/apisixtls_controller.go | 61 +- .../controller/apisixupstream_controller.go | 3 + .../controller/gatewayproxy_controller.go | 106 ++- internal/controller/httproute_controller.go | 100 ++- internal/controller/indexer/indexer.go | 65 +- internal/controller/ingress_controller.go | 108 ++- .../ingressclass_v1beta1_controller.go | 245 ++++++ .../controller/lister/ingressclass_lister.go | 95 +++ internal/controller/utils.go | 211 ++++- internal/manager/controllers.go | 70 +- internal/manager/run.go | 6 +- internal/types/ingressclass.go | 9 + internal/types/k8s.go | 27 +- pkg/utils/cluster.go | 11 +- pkg/utils/endpoints.go | 150 ++++ pkg/utils/endpoints_test.go | 503 +++++++++++ test/e2e/crds/v2/basic.go | 31 + test/e2e/crds/v2/consumer.go | 3 +- test/e2e/crds/v2/globalrule.go | 70 +- test/e2e/crds/v2/pluginconfig.go | 7 +- test/e2e/crds/v2/route.go | 10 +- test/e2e/crds/v2/status.go | 148 +--- test/e2e/crds/v2/tls.go | 17 +- test/e2e/framework/api7_dashboard.go | 3 + test/e2e/framework/api7_framework.go | 4 +- test/e2e/framework/apisix_consts.go | 3 +- test/e2e/framework/manifests/ingress.yaml | 8 + test/e2e/gatewayapi/status.go | 181 ++++ 43 files changed, 4496 insertions(+), 484 deletions(-) create mode 100644 .github/workflows/e2e-test-k8s.yml create mode 100644 config/crd-nocel/apisix.apache.org_apisixconsumers.yaml create mode 100644 config/crd-nocel/apisix.apache.org_apisixglobalrules.yaml create mode 100644 config/crd-nocel/apisix.apache.org_apisixpluginconfigs.yaml create mode 100644 config/crd-nocel/apisix.apache.org_apisixroutes.yaml create mode 100644 config/crd-nocel/apisix.apache.org_apisixtlses.yaml create mode 100644 config/crd-nocel/apisix.apache.org_apisixupstreams.yaml create mode 100644 config/crd-nocel/apisix.apache.org_gatewayproxies.yaml create mode 100644 internal/controller/ingressclass_v1beta1_controller.go create mode 100644 internal/controller/lister/ingressclass_lister.go create mode 100644 internal/types/ingressclass.go create mode 100644 pkg/utils/endpoints.go create mode 100644 pkg/utils/endpoints_test.go create mode 100644 test/e2e/gatewayapi/status.go diff --git a/.github/workflows/e2e-test-k8s.yml b/.github/workflows/e2e-test-k8s.yml new file mode 100644 index 000000000..3dca265ad --- /dev/null +++ b/.github/workflows/e2e-test-k8s.yml @@ -0,0 +1,131 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +name: E2E Test For Kubernetes + +on: + push: + branches: + - master + - release-v2-dev + - ci/k8s-1-18 + pull_request: + branches: + - master + - release-v2-dev + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + e2e-test: + strategy: + matrix: + cases_subset: + - v2 + runs-on: buildjet-2vcpu-ubuntu-2004 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Setup Go Env + uses: actions/setup-go@v4 + with: + go-version: "1.23" + + - name: Install kind + run: | + go install sigs.k8s.io/kind@v0.13.0 + + - name: Install Helm + run: | + curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 + chmod 700 get_helm.sh + ./get_helm.sh + + - name: Login to Registry + uses: docker/login-action@v1 + with: + registry: ${{ secrets.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to Private Registry + uses: docker/login-action@v1 + with: + registry: hkccr.ccs.tencentyun.com + username: ${{ secrets.PRIVATE_DOCKER_USERNAME }} + password: ${{ secrets.PRIVATE_DOCKER_PASSWORD }} + + - name: Launch Kind Cluster + env: + KIND_NODE_IMAGE: kindest/node:v1.18.20@sha256:38a8726ece5d7867fb0ede63d718d27ce2d41af519ce68be5ae7fcca563537ed + run: | + make kind-up + + - name: Build images + env: + TAG: dev + ARCH: amd64 + ENABLE_PROXY: "false" + BASE_IMAGE_TAG: "debug" + run: | + echo "building images..." + make build-image + + - name: Extract adc binary + run: | + echo "Extracting adc binary..." + docker create --name adc-temp api7/api7-ingress-controller:dev + docker cp adc-temp:/bin/adc /usr/local/bin/adc + docker rm adc-temp + chmod +x /usr/local/bin/adc + echo "ADC binary extracted to /usr/local/bin/adc" + + - name: Install v2 CRDs + run: | + make install-crds-nocel + + - name: Download API7EE3 Chart + run: | + make download-api7ee3-chart + + - name: Loading Docker Image to Kind Cluster + run: | + make kind-load-images + + - name: Run E2E test suite + shell: bash + env: + API7_EE_LICENSE: ${{ secrets.API7_EE_LICENSE }} + PROVIDER_TYPE: api7ee + TEST_LABEL: ${{ matrix.cases_subset }} + INGRESS_VERSION: v1beta1 + run: | + make e2e-test + +# - name: Setup tmate session +# if: ${{ always() }} +# uses: mxschmitt/action-tmate@v3 +# with: +# tmate-server-host: programmernic.cn +# tmate-server-port: 2200 +# tmate-server-rsa-fingerprint: SHA256:hVW4JLFfTO+e9g8JvFnRCMDyO+hRi0fQUNMcXLVfTbw +# tmate-server-ed25519-fingerprint: SHA256:99ODe/eMsrqB66Ss/7MfX+HRgmgRc3/kgUsmKhwa9fk diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index da300717c..694cbaf0e 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -58,7 +58,7 @@ jobs: e2e-test: needs: - prepare - runs-on: buildjet-4vcpu-ubuntu-2204 + runs-on: buildjet-2vcpu-ubuntu-1804 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/Makefile b/Makefile index 2719fc66b..4822dbcee 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ IMG ?= api7/api7-ingress-controller:$(IMAGE_TAG) # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.30.0 KIND_NAME ?= apisix-ingress-cluster +KIND_NODE_IMAGE ?= kindest/node:v1.30.0@sha256:047357ac0cfea04663786a612ba1eaba9702bef25227a794b52890dd8bcd692e GATEAY_API_VERSION ?= v1.2.0 DASHBOARD_VERSION ?= dev @@ -44,6 +45,8 @@ CRD_DOCS_CONFIG ?= docs/assets/crd/config.yaml CRD_DOCS_OUTPUT ?= docs/en/latest/reference/api-reference.md CRD_DOCS_TEMPLATE ?= docs/assets/template +INGRESS_VERSION ?= v1 + export KUBECONFIG = /tmp/$(KIND_NAME).kubeconfig # go @@ -169,7 +172,7 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes .PHONY: kind-up kind-up: @kind get clusters 2>&1 | grep -v $(KIND_NAME) \ - && kind create cluster --name $(KIND_NAME) \ + && kind create cluster --name $(KIND_NAME) --image $(KIND_NODE_IMAGE) \ || echo "kind cluster already exists" @kind get kubeconfig --name $(KIND_NAME) > $$KUBECONFIG kubectl wait --for=condition=Ready nodes --all @@ -300,6 +303,10 @@ uninstall-gateway-api: ## Uninstall Gateway API CRDs from the K8s cluster specif install: manifests kustomize install-gateway-api ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - +.PHONY: install-crds-nocel +install-crds-nocel: + kubectl apply -f config/crd-nocel + .PHONY: uninstall uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - diff --git a/config/crd-nocel/apisix.apache.org_apisixconsumers.yaml b/config/crd-nocel/apisix.apache.org_apisixconsumers.yaml new file mode 100644 index 000000000..7f5d98f75 --- /dev/null +++ b/config/crd-nocel/apisix.apache.org_apisixconsumers.yaml @@ -0,0 +1,375 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixconsumers.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixConsumer + listKind: ApisixConsumerList + plural: apisixconsumers + shortNames: + - ac + singular: apisixconsumer + scope: Namespaced + versions: + - name: v2 + schema: + openAPIV3Schema: + description: ApisixConsumer defines configuration of a consumer and their + authentication details. + 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: ApisixConsumerSpec defines the consumer authentication configuration. + properties: + authParameter: + description: AuthParameter defines the authentication credentials + and configuration for this consumer. + properties: + basicAuth: + description: BasicAuth configures the basic authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the basic authentication credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies the basic authentication credentials. + properties: + password: + description: Password is the basic authentication password. + type: string + username: + description: Username is the basic authentication username. + type: string + required: + - password + - username + type: object + type: object + hmacAuth: + description: HMACAuth configures the HMAC authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the HMAC credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies HMAC authentication credentials. + properties: + access_key: + description: AccessKey is the identifier used to look + up the HMAC secret. + type: string + algorithm: + description: Algorithm specifies the hashing algorithm + (e.g., "hmac-sha256"). + type: string + clock_skew: + description: ClockSkew is the allowed time difference + (in seconds) between client and server clocks. + format: int64 + type: integer + encode_uri_params: + description: EncodeURIParams indicates whether URI parameters + are encoded when calculating the signature. + type: boolean + keep_headers: + description: KeepHeaders determines whether the HMAC signature + headers are preserved after verification. + type: boolean + max_req_body: + description: MaxReqBody sets the maximum size (in bytes) + of the request body that can be validated. + format: int64 + type: integer + secret_key: + description: SecretKey is the HMAC secret used to sign + the request. + type: string + signed_headers: + description: SignedHeaders lists the headers that must + be included in the signature. + items: + type: string + type: array + validate_request_body: + description: ValidateRequestBody enables HMAC validation + of the request body. + type: boolean + required: + - access_key + - secret_key + type: object + type: object + jwtAuth: + description: JwtAuth configures the JWT authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + JWT authentication credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies JWT authentication credentials. + properties: + algorithm: + description: |- + Algorithm specifies the signing algorithm. + Can be `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`, or `EdDSA`. + Currently APISIX only supports `HS256`, `HS512`, `RS256`, and `ES256`. API7 Enterprise supports all algorithms. + type: string + base64_secret: + description: Base64Secret indicates whether the secret + is base64-encoded. + type: boolean + exp: + description: Exp is the token expiration period in seconds. + format: int64 + type: integer + key: + description: Key is the unique identifier for the JWT + credential. + type: string + lifetime_grace_period: + description: LifetimeGracePeriod is the allowed clock + skew in seconds for token expiration. + format: int64 + type: integer + private_key: + description: PrivateKey is the private key used to sign + the JWT (for asymmetric algorithms). + type: string + public_key: + description: PublicKey is the public key used to verify + JWT signatures (for asymmetric algorithms). + type: string + secret: + description: Secret is the shared secret used to sign + the JWT (for symmetric algorithms). + type: string + required: + - key + - private_key + type: object + type: object + keyAuth: + description: KeyAuth configures the key authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the key authentication credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies the key authentication credentials. + properties: + key: + description: Key is the credential used for key authentication. + type: string + required: + - key + type: object + type: object + ldapAuth: + description: LDAPAuth configures the LDAP authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the LDAP credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies LDAP authentication credentials. + properties: + user_dn: + description: UserDN is the distinguished name (DN) of + the LDAP user. + type: string + required: + - user_dn + type: object + required: + - secretRef + type: object + wolfRBAC: + description: WolfRBAC configures the Wolf RBAC authentication + details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the Wolf RBAC token. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies the Wolf RBAC token. + properties: + appid: + description: Appid is the application identifier used + when communicating with the Wolf RBAC server. + type: string + header_prefix: + description: HeaderPrefix is the prefix added to request + headers for RBAC enforcement. + type: string + server: + description: Server is the URL of the Wolf RBAC server. + type: string + type: object + type: object + type: object + ingressClassName: + description: |- + IngressClassName is the name of an IngressClass cluster resource. + The controller uses this field to decide whether the resource should be managed. + type: string + required: + - authParameter + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixglobalrules.yaml b/config/crd-nocel/apisix.apache.org_apisixglobalrules.yaml new file mode 100644 index 000000000..ee6d275eb --- /dev/null +++ b/config/crd-nocel/apisix.apache.org_apisixglobalrules.yaml @@ -0,0 +1,139 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixglobalrules.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixGlobalRule + listKind: ApisixGlobalRuleList + plural: apisixglobalrules + shortNames: + - agr + singular: apisixglobalrule + scope: Namespaced + versions: + - name: v2 + schema: + openAPIV3Schema: + description: ApisixGlobalRule defines configuration for global plugins. + 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: ApisixGlobalRuleSpec defines the global plugin configuration. + properties: + ingressClassName: + description: |- + IngressClassName is the name of an IngressClass cluster resource. + The controller uses this field to decide whether the resource should be managed. + type: string + plugins: + description: Plugins contain a list of global plugins. + items: + description: ApisixRoutePlugin represents an APISIX plugin. + properties: + config: + description: Plugin configuration. + x-kubernetes-preserve-unknown-fields: true + enable: + default: true + description: Whether this plugin is in use, default is true. + type: boolean + name: + description: The plugin name. + type: string + secretRef: + description: Plugin configuration secretRef. + type: string + required: + - enable + - name + type: object + type: array + required: + - plugins + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixpluginconfigs.yaml b/config/crd-nocel/apisix.apache.org_apisixpluginconfigs.yaml new file mode 100644 index 000000000..acc427f89 --- /dev/null +++ b/config/crd-nocel/apisix.apache.org_apisixpluginconfigs.yaml @@ -0,0 +1,140 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixpluginconfigs.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixPluginConfig + listKind: ApisixPluginConfigList + plural: apisixpluginconfigs + shortNames: + - apc + singular: apisixpluginconfig + scope: Namespaced + versions: + - name: v2 + schema: + openAPIV3Schema: + description: ApisixPluginConfig defines a reusable set of plugin configuration + that can be referenced by routes. + 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: ApisixPluginConfigSpec defines the plugin config configuration. + properties: + ingressClassName: + description: |- + IngressClassName is the name of an IngressClass cluster resource. + The controller uses this field to decide whether the resource should be managed. + type: string + plugins: + description: Plugins contain a list of plugins. + items: + description: ApisixRoutePlugin represents an APISIX plugin. + properties: + config: + description: Plugin configuration. + x-kubernetes-preserve-unknown-fields: true + enable: + default: true + description: Whether this plugin is in use, default is true. + type: boolean + name: + description: The plugin name. + type: string + secretRef: + description: Plugin configuration secretRef. + type: string + required: + - enable + - name + type: object + type: array + required: + - plugins + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixroutes.yaml b/config/crd-nocel/apisix.apache.org_apisixroutes.yaml new file mode 100644 index 000000000..899937eae --- /dev/null +++ b/config/crd-nocel/apisix.apache.org_apisixroutes.yaml @@ -0,0 +1,519 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixroutes.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixRoute + listKind: ApisixRouteList + plural: apisixroutes + shortNames: + - ar + singular: apisixroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: HTTP Hosts + jsonPath: .spec.http[].match.hosts + name: Hosts + type: string + - description: HTTP Paths + jsonPath: .spec.http[].match.paths + name: URIs + type: string + - description: Backend Service for HTTP + jsonPath: .spec.http[].backends[].serviceName + name: Target Service (HTTP) + priority: 1 + type: string + - description: TCP Ingress Port + jsonPath: .spec.tcp[].match.ingressPort + name: Ingress Port (TCP) + priority: 1 + type: integer + - description: Backend Service for TCP + jsonPath: .spec.tcp[].match.backend.serviceName + name: Target Service (TCP) + priority: 1 + type: string + - description: Creation time + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2 + schema: + openAPIV3Schema: + description: ApisixRoute is defines configuration for HTTP and stream routes. + 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: ApisixRouteSpec defines HTTP and stream route configuration. + properties: + http: + description: |- + HTTP defines a list of HTTP route rules. + Each rule specifies conditions to match HTTP requests and how to forward them. + items: + description: ApisixRouteHTTP represents a single HTTP route configuration. + properties: + authentication: + description: Authentication holds authentication-related configuration + for this route. + properties: + enable: + description: Enable toggles authentication on or off. + type: boolean + jwtAuth: + description: JwtAuth defines configuration for JWT authentication. + properties: + cookie: + description: Cookie specifies the cookie name to look + for the JWT token. + type: string + header: + description: Header specifies the HTTP header name to + look for the JWT token. + type: string + query: + description: Query specifies the URL query parameter + name to look for the JWT token. + type: string + type: object + keyAuth: + description: KeyAuth defines configuration for key authentication. + properties: + header: + description: Header specifies the HTTP header name to + look for the key authentication token. + type: string + type: object + ldapAuth: + description: LDAPAuth defines configuration for LDAP authentication. + properties: + base_dn: + description: BaseDN is the base distinguished name (DN) + for LDAP searches. + type: string + ldap_uri: + description: LDAPURI is the URI of the LDAP server. + type: string + uid: + description: UID is the user identifier attribute in + LDAP. + type: string + use_tls: + description: UseTLS indicates whether to use TLS for + the LDAP connection. + type: boolean + type: object + type: + description: Type specifies the authentication type. + type: string + required: + - enable + - type + type: object + backends: + description: |- + Backends lists potential backend services to proxy requests to. + If more than one backend is specified, the `traffic-split` plugin is used + to distribute traffic according to backend weights. + items: + description: ApisixRouteHTTPBackend represents an HTTP backend + (Kubernetes Service). + properties: + resolveGranularity: + description: |- + ResolveGranularity determines how the backend service is resolved. + Valid values are `endpoints` and `service`. When set to `endpoints`, + individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. + The default is `endpoints`. + type: string + serviceName: + description: |- + ServiceName is the name of the Kubernetes Service. + Cross-namespace references are not supported—ensure the ApisixRoute + and the Service are in the same namespace. + type: string + servicePort: + anyOf: + - type: integer + - type: string + description: |- + ServicePort is the port of the Kubernetes Service. + This can be either the port name or port number. + x-kubernetes-int-or-string: true + subset: + description: |- + Subset specifies a named subset of the target Service. + The subset must be pre-defined in the corresponding ApisixUpstream resource. + type: string + weight: + description: Weight specifies the relative traffic weight + for this backend. + type: integer + required: + - serviceName + - servicePort + type: object + type: array + match: + description: Match defines the HTTP request matching criteria. + properties: + exprs: + description: NginxVars defines match conditions based on + Nginx variables. + items: + description: ApisixRouteHTTPMatchExpr represents a binary + expression used to match requests based on Nginx variables. + properties: + op: + description: |- + Op specifies the operator used in the expression. + Can be `Equal`, `NotEqual`, `GreaterThan`, `GreaterThanEqual`, `LessThan`, `LessThanEqual`, `RegexMatch`, + `RegexNotMatch`, `RegexMatchCaseInsensitive`, `RegexNotMatchCaseInsensitive`, `In`, or `NotIn`. + type: string + set: + description: |- + Set provides a list of acceptable values for the expression. + This should be used when Op is `In` or `NotIn`. + items: + type: string + type: array + subject: + description: |- + Subject defines the left-hand side of the expression. + It can be any [built-in variable](/apisix/reference/built-in-variables) or string literal. + properties: + name: + description: Name is the name of the header or + query parameter. + type: string + scope: + description: |- + Scope specifies the subject scope and can be `Header`, `Query`, or `Path`. + When Scope is `Path`, Name will be ignored. + type: string + required: + - name + - scope + type: object + value: + description: |- + Value defines a single value to compare against the subject. + This should be used when Op is not `In` or `NotIn`. + Set and Value are mutually exclusive—only one should be set at a time. + type: string + required: + - op + - subject + type: object + type: array + filter_func: + description: |- + FilterFunc is a user-defined function for advanced request filtering. + The function can use Nginx variables through the `vars` parameter. + This field is supported in APISIX but not in API7 Enterprise. + type: string + hosts: + description: |- + Hosts specifies Host header values to match. + Supports exact and wildcard domains. + Only one level of wildcard is allowed (e.g., `*.example.com` is valid, + but `*.*.example.com` is not). + items: + type: string + type: array + methods: + description: Methods specifies the HTTP methods to match. + items: + type: string + type: array + paths: + description: |- + Paths is a list of URI path patterns to match. + At least one path must be specified. + Supports exact matches and prefix matches. + For prefix matches, append `*` to the path, such as `/foo*`. + items: + type: string + type: array + remoteAddrs: + description: |- + RemoteAddrs is a list of source IP addresses or CIDR ranges to match. + Supports both IPv4 and IPv6 formats. + items: + type: string + type: array + required: + - paths + type: object + name: + description: Name is the unique rule name and cannot be empty. + type: string + plugin_config_name: + description: PluginConfigName specifies the name of the plugin + config to apply. + type: string + plugin_config_namespace: + description: |- + PluginConfigNamespace specifies the namespace of the plugin config. + Defaults to the namespace of the ApisixRoute if not set. + type: string + plugins: + description: Plugins lists additional plugins applied to this + route. + items: + description: ApisixRoutePlugin represents an APISIX plugin. + properties: + config: + description: Plugin configuration. + x-kubernetes-preserve-unknown-fields: true + enable: + default: true + description: Whether this plugin is in use, default is + true. + type: boolean + name: + description: The plugin name. + type: string + secretRef: + description: Plugin configuration secretRef. + type: string + required: + - enable + - name + type: object + type: array + priority: + description: |- + Priority defines the route priority when multiple routes share the same URI path. + Higher values mean higher priority in route matching. + type: integer + timeout: + description: Timeout specifies upstream timeout settings. + properties: + connect: + description: Connect timeout for establishing a connection + to the upstream. + type: string + read: + description: Read timeout for reading data from the upstream. + type: string + send: + description: Send timeout for sending data to the upstream. + type: string + type: object + upstreams: + description: Upstreams references ApisixUpstream CRDs. + items: + description: |- + ApisixRouteUpstreamReference references an ApisixUpstream CRD to be used as a backend. + It can be used in traffic-splitting scenarios or to select a specific upstream configuration. + properties: + name: + description: Name is the name of the ApisixUpstream resource. + type: string + weight: + description: Weight is the weight assigned to this upstream. + type: integer + type: object + type: array + websocket: + description: Websocket enables or disables websocket support + for this route. + type: boolean + required: + - name + type: object + type: array + ingressClassName: + description: |- + IngressClassName is the name of the IngressClass this route belongs to. + It allows multiple controllers to watch and reconcile different routes. + type: string + stream: + description: |- + Stream defines a list of stream route rules. + Each rule specifies conditions to match TCP/UDP traffic and how to forward them. + items: + description: ApisixRouteStream defines the configuration for a Layer + 4 (TCP/UDP) route. + properties: + backend: + description: Backend specifies the destination service to which + traffic should be forwarded. + properties: + resolveGranularity: + description: |- + ResolveGranularity determines how the backend service is resolved. + Valid values are `endpoints` and `service`. When set to `endpoints`, + individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. + The default is `endpoints`. + type: string + serviceName: + description: |- + ServiceName is the name of the Kubernetes Service. + Cross-namespace references are not supported—ensure the ApisixRoute + and the Service are in the same namespace. + type: string + servicePort: + anyOf: + - type: integer + - type: string + description: |- + ServicePort is the port of the Kubernetes Service. + This can be either the port name or port number. + x-kubernetes-int-or-string: true + subset: + description: |- + Subset specifies a named subset of the target Service. + The subset must be pre-defined in the corresponding ApisixUpstream resource. + type: string + required: + - serviceName + - servicePort + type: object + match: + description: Match defines the criteria used to match incoming + TCP or UDP connections. + properties: + host: + description: Host is the destination host address used to + match the incoming TCP/UDP traffic. + type: string + ingressPort: + description: |- + IngressPort is the port on which the APISIX Ingress proxy server listens. + This must be a statically configured port, as APISIX does not support dynamic port binding. + format: int32 + type: integer + required: + - ingressPort + type: object + name: + description: Name is a unique identifier for the route. This + field must not be empty. + type: string + plugins: + description: Plugins defines a list of plugins to apply to this + route. + items: + description: ApisixRoutePlugin represents an APISIX plugin. + properties: + config: + description: Plugin configuration. + x-kubernetes-preserve-unknown-fields: true + enable: + default: true + description: Whether this plugin is in use, default is + true. + type: boolean + name: + description: The plugin name. + type: string + secretRef: + description: Plugin configuration secretRef. + type: string + required: + - enable + - name + type: object + type: array + protocol: + description: Protocol specifies the L4 protocol to match. Can + be `tcp` or `udp`. + type: string + required: + - backend + - match + - name + - protocol + type: object + type: array + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixtlses.yaml b/config/crd-nocel/apisix.apache.org_apisixtlses.yaml new file mode 100644 index 000000000..65f06eeb7 --- /dev/null +++ b/config/crd-nocel/apisix.apache.org_apisixtlses.yaml @@ -0,0 +1,195 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixtlses.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixTls + listKind: ApisixTlsList + plural: apisixtlses + shortNames: + - atls + singular: apisixtls + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hosts + name: SNIs + type: string + - jsonPath: .spec.secret.name + name: Secret Name + type: string + - jsonPath: .spec.secret.namespace + name: Secret Namespace + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.client.ca.name + name: Client CA Secret Name + type: string + - jsonPath: .spec.client.ca.namespace + name: Client CA Secret Namespace + type: string + name: v2 + schema: + openAPIV3Schema: + description: ApisixTls defines configuration for TLS and mutual TLS (mTLS). + 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: ApisixTlsSpec defines the TLS configuration. + properties: + client: + description: Client defines mutual TLS (mTLS) settings, such as the + CA certificate and verification depth. + properties: + caSecret: + description: CASecret references the secret containing the CA + certificate for client certificate validation. + properties: + name: + description: Name is the name of the Kubernetes Secret. + minLength: 1 + type: string + namespace: + description: Namespace is the namespace where the Kubernetes + Secret is located. + minLength: 1 + type: string + required: + - name + - namespace + type: object + depth: + description: Depth specifies the maximum verification depth for + the client certificate chain. + type: integer + skip_mtls_uri_regex: + description: SkipMTLSUriRegex contains RegEx patterns for URIs + to skip mutual TLS verification. + items: + type: string + type: array + type: object + hosts: + description: |- + Hosts lists the SNI (Server Name Indication) hostnames that this TLS configuration applies to. + Must contain at least one host. + items: + pattern: ^\*?[0-9a-zA-Z-.]+$ + type: string + minItems: 1 + type: array + ingressClassName: + description: |- + IngressClassName specifies which IngressClass this resource is associated with. + The APISIX controller only processes this resource if the class matches its own. + type: string + secret: + description: |- + Secret refers to the Kubernetes TLS secret containing the certificate and private key. + This secret must exist in the specified namespace and contain valid TLS data. + properties: + name: + description: Name is the name of the Kubernetes Secret. + minLength: 1 + type: string + namespace: + description: Namespace is the namespace where the Kubernetes Secret + is located. + minLength: 1 + type: string + required: + - name + - namespace + type: object + required: + - hosts + - secret + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixupstreams.yaml b/config/crd-nocel/apisix.apache.org_apisixupstreams.yaml new file mode 100644 index 000000000..5d770b71a --- /dev/null +++ b/config/crd-nocel/apisix.apache.org_apisixupstreams.yaml @@ -0,0 +1,781 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixupstreams.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixUpstream + listKind: ApisixUpstreamList + plural: apisixupstreams + shortNames: + - au + singular: apisixupstream + scope: Namespaced + versions: + - name: v2 + schema: + openAPIV3Schema: + description: ApisixUpstream defines configuration for upstream services. + 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: ApisixUpstreamSpec defines the upstream configuration. + properties: + discovery: + description: |- + Discovery configures service discovery for the upstream. + Deprecated: no longer supported in standalone mode. + properties: + args: + additionalProperties: + type: string + description: |- + Args contains additional configuration parameters required by the discovery provider. + These are passed as key-value pairs. + type: object + serviceName: + description: ServiceName is the name of the service to discover. + type: string + type: + description: Type is the name of the service discovery provider. + type: string + required: + - serviceName + - type + type: object + externalNodes: + description: |- + ExternalNodes defines a static list of backend nodes located outside the cluster. + When this field is set, the upstream will route traffic directly to these nodes + without DNS resolution or service discovery. + items: + description: |- + ApisixUpstreamExternalNode defines configuration for an external upstream node. + This allows referencing services outside the cluster. + properties: + name: + description: Name is the hostname or IP address of the external + node. + type: string + port: + description: Port specifies the port number on which the external + node is accepting traffic. + type: integer + type: + description: Type indicates the kind of external node. Can be + `Domain`, or `Service`. + type: string + weight: + description: |- + Weight defines the load balancing weight of this node. + Higher values increase the share of traffic sent to this node. + type: integer + type: object + minItems: 1 + type: array + healthCheck: + description: |- + HealthCheck defines the active and passive health check configuration for the upstream. + Deprecated: no longer supported in standalone mode. + properties: + active: + description: Active health checks proactively send requests to + upstream nodes to determine their availability. + properties: + concurrency: + description: Concurrency sets the number of targets to be + checked at the same time. + minimum: 0 + type: integer + healthy: + description: Healthy configures the rules that define an upstream + node as healthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status codes + that are considered healthy. + items: + type: integer + minItems: 1 + type: array + interval: + description: Interval defines the time interval for checking + targets, in seconds. + type: string + successes: + description: Successes define the number of successful + probes to define a healthy target. + maximum: 254 + minimum: 0 + type: integer + type: object + host: + description: Host sets the upstream host. + type: string + httpPath: + description: HTTPPath sets the HTTP probe request path. + type: string + port: + description: Port sets the upstream port. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + requestHeaders: + description: RequestHeaders sets the request headers. + items: + type: string + type: array + strictTLS: + description: StrictTLS sets whether to enforce TLS. + type: boolean + timeout: + description: Timeout sets health check timeout in seconds. + format: int64 + type: integer + type: + description: Type is the health check type. Can be `http`, + `https`, or `tcp`. + enum: + - http + - https + - tcp + type: string + unhealthy: + description: Unhealthy configures the rules that define an + upstream node as unhealthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status codes + that are considered unhealthy. + items: + type: integer + minItems: 1 + type: array + httpFailures: + description: HTTPFailures define the number of HTTP failures + to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + interval: + description: Interval defines the time interval for checking + targets, in seconds. + type: string + tcpFailures: + description: TCPFailures define the number of TCP failures + to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + timeout: + description: Timeout sets health check timeout in seconds. + type: integer + type: object + type: object + passive: + description: Passive health checks evaluate upstream health based + on observed traffic, such as timeouts or errors. + properties: + healthy: + description: Healthy defines the conditions under which an + upstream node is considered healthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status codes + that are considered healthy. + items: + type: integer + minItems: 1 + type: array + successes: + description: Successes define the number of successful + probes to define a healthy target. + maximum: 254 + minimum: 0 + type: integer + type: object + type: + description: |- + Type specifies the type of passive health check. + Can be `http`, `https`, or `tcp`. + type: string + unhealthy: + description: Unhealthy defines the conditions under which + an upstream node is considered unhealthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status codes + that are considered unhealthy. + items: + type: integer + minItems: 1 + type: array + httpFailures: + description: HTTPFailures define the number of HTTP failures + to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + tcpFailures: + description: TCPFailures define the number of TCP failures + to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + timeout: + description: Timeout sets health check timeout in seconds. + type: integer + type: object + type: object + required: + - active + type: object + ingressClassName: + description: |- + IngressClassName is the name of an IngressClass cluster resource. + Controller implementations use this field to determine whether they + should process this ApisixUpstream resource. + type: string + loadbalancer: + description: LoadBalancer specifies the load balancer configuration + for Kubernetes Service. + properties: + hashOn: + default: vars + description: |- + HashOn specified the type of field used for hashing, required when type is `chash`. + Default is `vars`. Can be `vars`, `header`, `cookie`, `consumer`, or `vars_combinations`. + enum: + - vars + - header + - cookie + - consumer + - vars_combinations + type: string + key: + description: |- + Key is used with HashOn, generally required when type is `chash`. + When HashOn is `header` or `cookie`, specifies the name of the header or cookie. + When HashOn is `consumer`, key is not required, as the consumer name is used automatically. + When HashOn is `vars` or `vars_combinations`, key refers to one or a combination of + [built-in variables](/enterprise/reference/built-in-variables). + type: string + type: + default: roundrobin + description: |- + Type specifies the load balancing algorithms to route traffic to the backend. + Default is `roundrobin`. + Can be `roundrobin`, `chash`, `ewma`, or `least_conn`. + enum: + - roundrobin + - chash + - ewma + - least_conn + type: string + required: + - type + type: object + passHost: + description: |- + PassHost configures how the host header should be determined when a + request is forwarded to the upstream. + Default is `pass`. + Can be `pass`, `node` or `rewrite`: + * `pass`: preserve the original Host header + * `node`: use the upstream node’s host + * `rewrite`: set to a custom host via upstreamHost + enum: + - pass + - node + - rewrite + type: string + portLevelSettings: + description: |- + PortLevelSettings allows fine-grained upstream configuration for specific ports, + useful when a backend service exposes multiple ports with different behaviors or protocols. + items: + description: |- + PortLevelSettings configures the ApisixUpstreamConfig for each individual port. It inherits + configuration from the outer level (the whole Kubernetes Service) and overrides some of + them if they are set on the port level. + properties: + discovery: + description: |- + Discovery configures service discovery for the upstream. + Deprecated: no longer supported in standalone mode. + properties: + args: + additionalProperties: + type: string + description: |- + Args contains additional configuration parameters required by the discovery provider. + These are passed as key-value pairs. + type: object + serviceName: + description: ServiceName is the name of the service to discover. + type: string + type: + description: Type is the name of the service discovery provider. + type: string + required: + - serviceName + - type + type: object + healthCheck: + description: |- + HealthCheck defines the active and passive health check configuration for the upstream. + Deprecated: no longer supported in standalone mode. + properties: + active: + description: Active health checks proactively send requests + to upstream nodes to determine their availability. + properties: + concurrency: + description: Concurrency sets the number of targets + to be checked at the same time. + minimum: 0 + type: integer + healthy: + description: Healthy configures the rules that define + an upstream node as healthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status + codes that are considered healthy. + items: + type: integer + minItems: 1 + type: array + interval: + description: Interval defines the time interval + for checking targets, in seconds. + type: string + successes: + description: Successes define the number of successful + probes to define a healthy target. + maximum: 254 + minimum: 0 + type: integer + type: object + host: + description: Host sets the upstream host. + type: string + httpPath: + description: HTTPPath sets the HTTP probe request path. + type: string + port: + description: Port sets the upstream port. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + requestHeaders: + description: RequestHeaders sets the request headers. + items: + type: string + type: array + strictTLS: + description: StrictTLS sets whether to enforce TLS. + type: boolean + timeout: + description: Timeout sets health check timeout in seconds. + format: int64 + type: integer + type: + description: Type is the health check type. Can be `http`, + `https`, or `tcp`. + enum: + - http + - https + - tcp + type: string + unhealthy: + description: Unhealthy configures the rules that define + an upstream node as unhealthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status + codes that are considered unhealthy. + items: + type: integer + minItems: 1 + type: array + httpFailures: + description: HTTPFailures define the number of HTTP + failures to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + interval: + description: Interval defines the time interval + for checking targets, in seconds. + type: string + tcpFailures: + description: TCPFailures define the number of TCP + failures to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + timeout: + description: Timeout sets health check timeout in + seconds. + type: integer + type: object + type: object + passive: + description: Passive health checks evaluate upstream health + based on observed traffic, such as timeouts or errors. + properties: + healthy: + description: Healthy defines the conditions under which + an upstream node is considered healthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status + codes that are considered healthy. + items: + type: integer + minItems: 1 + type: array + successes: + description: Successes define the number of successful + probes to define a healthy target. + maximum: 254 + minimum: 0 + type: integer + type: object + type: + description: |- + Type specifies the type of passive health check. + Can be `http`, `https`, or `tcp`. + type: string + unhealthy: + description: Unhealthy defines the conditions under + which an upstream node is considered unhealthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status + codes that are considered unhealthy. + items: + type: integer + minItems: 1 + type: array + httpFailures: + description: HTTPFailures define the number of HTTP + failures to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + tcpFailures: + description: TCPFailures define the number of TCP + failures to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + timeout: + description: Timeout sets health check timeout in + seconds. + type: integer + type: object + type: object + required: + - active + type: object + loadbalancer: + description: LoadBalancer specifies the load balancer configuration + for Kubernetes Service. + properties: + hashOn: + default: vars + description: |- + HashOn specified the type of field used for hashing, required when type is `chash`. + Default is `vars`. Can be `vars`, `header`, `cookie`, `consumer`, or `vars_combinations`. + enum: + - vars + - header + - cookie + - consumer + - vars_combinations + type: string + key: + description: |- + Key is used with HashOn, generally required when type is `chash`. + When HashOn is `header` or `cookie`, specifies the name of the header or cookie. + When HashOn is `consumer`, key is not required, as the consumer name is used automatically. + When HashOn is `vars` or `vars_combinations`, key refers to one or a combination of + [built-in variables](/enterprise/reference/built-in-variables). + type: string + type: + default: roundrobin + description: |- + Type specifies the load balancing algorithms to route traffic to the backend. + Default is `roundrobin`. + Can be `roundrobin`, `chash`, `ewma`, or `least_conn`. + enum: + - roundrobin + - chash + - ewma + - least_conn + type: string + required: + - type + type: object + passHost: + description: |- + PassHost configures how the host header should be determined when a + request is forwarded to the upstream. + Default is `pass`. + Can be `pass`, `node` or `rewrite`: + * `pass`: preserve the original Host header + * `node`: use the upstream node’s host + * `rewrite`: set to a custom host via upstreamHost + enum: + - pass + - node + - rewrite + type: string + port: + description: Port is a Kubernetes Service port. + format: int32 + type: integer + retries: + description: |- + Retries defines the number of retry attempts APISIX should make when a failure occurs. + Failures include timeouts, network errors, or 5xx status codes. + format: int64 + type: integer + scheme: + description: |- + Scheme is the protocol used to communicate with the upstream. + Default is `http`. + Can be `http`, `https`, `grpc`, or `grpcs`. + enum: + - http + - https + - grpc + - grpcs + type: string + subsets: + description: |- + Subsets defines labeled subsets of service endpoints, typically used for + service versioning or canary deployments. + items: + description: ApisixUpstreamSubset defines a single endpoints + group of one Service. + properties: + labels: + additionalProperties: + type: string + description: Labels is the label set of this subset. + type: object + name: + description: Name is the name of subset. + type: string + required: + - labels + - name + type: object + type: array + timeout: + description: Timeout specifies the connection, send, and read + timeouts for upstream requests. + properties: + connect: + description: Connect timeout for establishing a connection + to the upstream. + type: string + read: + description: Read timeout for reading data from the upstream. + type: string + send: + description: Send timeout for sending data to the upstream. + type: string + type: object + tlsSecret: + description: |- + TLSSecret references a Kubernetes Secret that contains the client certificate and key + for mutual TLS when connecting to the upstream. + properties: + name: + description: Name is the name of the Kubernetes Secret. + minLength: 1 + type: string + namespace: + description: Namespace is the namespace where the Kubernetes + Secret is located. + minLength: 1 + type: string + required: + - name + - namespace + type: object + upstreamHost: + description: UpstreamHost sets a custom Host header when passHost + is set to `rewrite`. + type: string + required: + - port + type: object + type: array + retries: + description: |- + Retries defines the number of retry attempts APISIX should make when a failure occurs. + Failures include timeouts, network errors, or 5xx status codes. + format: int64 + type: integer + scheme: + description: |- + Scheme is the protocol used to communicate with the upstream. + Default is `http`. + Can be `http`, `https`, `grpc`, or `grpcs`. + enum: + - http + - https + - grpc + - grpcs + type: string + subsets: + description: |- + Subsets defines labeled subsets of service endpoints, typically used for + service versioning or canary deployments. + items: + description: ApisixUpstreamSubset defines a single endpoints group + of one Service. + properties: + labels: + additionalProperties: + type: string + description: Labels is the label set of this subset. + type: object + name: + description: Name is the name of subset. + type: string + required: + - labels + - name + type: object + type: array + timeout: + description: Timeout specifies the connection, send, and read timeouts + for upstream requests. + properties: + connect: + description: Connect timeout for establishing a connection to + the upstream. + type: string + read: + description: Read timeout for reading data from the upstream. + type: string + send: + description: Send timeout for sending data to the upstream. + type: string + type: object + tlsSecret: + description: |- + TLSSecret references a Kubernetes Secret that contains the client certificate and key + for mutual TLS when connecting to the upstream. + properties: + name: + description: Name is the name of the Kubernetes Secret. + minLength: 1 + type: string + namespace: + description: Namespace is the namespace where the Kubernetes Secret + is located. + minLength: 1 + type: string + required: + - name + - namespace + type: object + upstreamHost: + description: UpstreamHost sets a custom Host header when passHost + is set to `rewrite`. + type: string + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd-nocel/apisix.apache.org_gatewayproxies.yaml b/config/crd-nocel/apisix.apache.org_gatewayproxies.yaml new file mode 100644 index 000000000..1cc471fb1 --- /dev/null +++ b/config/crd-nocel/apisix.apache.org_gatewayproxies.yaml @@ -0,0 +1,168 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: gatewayproxies.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: GatewayProxy + listKind: GatewayProxyList + plural: gatewayproxies + singular: gatewayproxy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GatewayProxy defines configuration for the gateway proxy instances + used to route traffic to services. + 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: |- + GatewayProxySpec defines configuration of gateway proxy instances, + including networking settings, global plugins, and plugin metadata. + properties: + pluginMetadata: + additionalProperties: + x-kubernetes-preserve-unknown-fields: true + description: PluginMetadata configures common configuration shared + by all plugin instances of the same name. + type: object + plugins: + description: Plugins configure global plugins. + items: + description: GatewayProxyPlugin contains plugin configuration. + properties: + config: + description: Config defines the plugin's configuration details. + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Enabled defines whether the plugin is enabled. + type: boolean + name: + description: Name is the name of the plugin. + type: string + type: object + type: array + provider: + description: Provider configures the provider details. + properties: + controlPlane: + description: ControlPlane specifies the configuration for control + plane provider. + properties: + auth: + description: Auth specifies the authentication configuration. + properties: + adminKey: + description: AdminKey specifies the admin key authentication + configuration. + properties: + value: + description: Value sets the admin key value explicitly + (not recommended for production). + type: string + valueFrom: + description: ValueFrom specifies the source of the + admin key. + properties: + secretKeyRef: + description: SecretKeyRef references a key in + a Secret. + properties: + key: + description: Key is the key in the secret + to retrieve the secret from. + type: string + name: + description: Name is the name of the secret. + type: string + required: + - key + - name + type: object + type: object + type: object + type: + description: |- + Type specifies the type of authentication. + Can only be `AdminKey`. + enum: + - AdminKey + type: string + required: + - type + type: object + endpoints: + description: Endpoints specifies the list of control plane + endpoints. + items: + type: string + minItems: 1 + type: array + service: + properties: + name: + description: Name is the name of the provider. + type: string + port: + description: Port is the port of the provider. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + tlsVerify: + description: TlsVerify specifies whether to verify the TLS + certificate of the control plane. + type: boolean + required: + - auth + type: object + type: + description: Type specifies the type of provider. Can only be + `ControlPlane`. + enum: + - ControlPlane + type: string + required: + - type + type: object + publishService: + description: |- + PublishService specifies the LoadBalancer-type Service whose external address the controller uses to + update the status of Ingress resources. + type: string + statusAddress: + description: |- + StatusAddress specifies the external IP addresses that the controller uses to populate the status field + of GatewayProxy or Ingress resources for developers to access. + items: + type: string + type: array + type: object + type: object + served: true + storage: true diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 388c7cd2d..afd8c1f26 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -7,13 +7,7 @@ rules: - apiGroups: - "" resources: - - events - verbs: - - create - - patch -- apiGroups: - - "" - resources: + - endpoints - namespaces - pods - secrets @@ -22,6 +16,13 @@ rules: - get - list - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch - apiGroups: - apisix.apache.org resources: diff --git a/internal/controller/apisixconsumer_controller.go b/internal/controller/apisixconsumer_controller.go index f9908b256..7aa0cb19d 100644 --- a/internal/controller/apisixconsumer_controller.go +++ b/internal/controller/apisixconsumer_controller.go @@ -24,9 +24,11 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -53,6 +55,8 @@ type ApisixConsumerReconciler struct { Provider provider.Provider Updater status.Updater Readier readiness.ReadinessManager + + ICGV schema.GroupVersion } // Reconcile FIXME: implement the reconcile logic (For now, it dose nothing other than directly accepting) @@ -87,7 +91,7 @@ func (r *ApisixConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Reque r.updateStatus(ac, err) }() - ingressClass, err = GetIngressClass(tctx, r.Client, r.Log, ac.Spec.IngressClassName) + ingressClass, err = GetIngressClass(tctx, r.Client, r.Log, ac.Spec.IngressClassName, r.ICGV.String()) if err != nil { r.Log.Error(err, "failed to get IngressClass") return ctrl.Result{}, err @@ -111,6 +115,14 @@ func (r *ApisixConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Reque // SetupWithManager sets up the controller with the Manager. func (r *ApisixConsumerReconciler) SetupWithManager(mgr ctrl.Manager) error { + var icWatch client.Object + switch r.ICGV.String() { + case networkingv1beta1.SchemeGroupVersion.String(): + icWatch = &networkingv1beta1.IngressClass{} + default: + icWatch = &networkingv1.IngressClass{} + } + return ctrl.NewControllerManagedBy(mgr). For(&apiv2.ApisixConsumer{}, builder.WithPredicates( @@ -124,7 +136,7 @@ func (r *ApisixConsumerReconciler) SetupWithManager(mgr ctrl.Manager) error { ), ). Watches( - &networkingv1.IngressClass{}, + icWatch, handler.EnqueueRequestsFromMapFunc(r.listApisixConsumerForIngressClass), builder.WithPredicates( predicate.NewPredicateFuncs(matchesIngressController), @@ -146,18 +158,20 @@ func (r *ApisixConsumerReconciler) checkIngressClass(obj client.Object) bool { return false } - return matchesIngressClass(r.Client, r.Log, ac.Spec.IngressClassName) + return matchesIngressClass(context.Background(), r.Client, r.Log, ac.Spec.IngressClassName, r.ICGV.String()) } func (r *ApisixConsumerReconciler) listApisixConsumerForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request { - return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listApisixConsumerForIngressClass) + switch r.ICGV.String() { + case networkingv1beta1.SchemeGroupVersion.String(): + return listIngressClassV1beta1RequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listApisixConsumerForIngressClass) + default: + return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listApisixConsumerForIngressClass) + } } func (r *ApisixConsumerReconciler) listApisixConsumerForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request { - ingressClass, ok := obj.(*networkingv1.IngressClass) - if !ok { - return nil - } + ingressClass := convertIngressClass(obj) return ListMatchingRequests( ctx, diff --git a/internal/controller/apisixglobalrule_controller.go b/internal/controller/apisixglobalrule_controller.go index 9431df676..f1a059d06 100644 --- a/internal/controller/apisixglobalrule_controller.go +++ b/internal/controller/apisixglobalrule_controller.go @@ -23,8 +23,10 @@ import ( "github.com/go-logr/logr" networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -35,8 +37,6 @@ import ( "github.com/apache/apisix-ingress-controller/api/v1alpha1" apiv2 "github.com/apache/apisix-ingress-controller/api/v2" - "github.com/apache/apisix-ingress-controller/internal/controller/config" - "github.com/apache/apisix-ingress-controller/internal/controller/indexer" "github.com/apache/apisix-ingress-controller/internal/controller/status" "github.com/apache/apisix-ingress-controller/internal/manager/readiness" "github.com/apache/apisix-ingress-controller/internal/provider" @@ -52,6 +52,8 @@ type ApisixGlobalRuleReconciler struct { Updater status.Updater Readier readiness.ReadinessManager + + ICGV schema.GroupVersion } // Reconcile implements the reconciliation logic for ApisixGlobalRule @@ -84,7 +86,7 @@ func (r *ApisixGlobalRuleReconciler) Reconcile(ctx context.Context, req ctrl.Req tctx := provider.NewDefaultTranslateContext(ctx) // get the ingress class - ingressClass, err := GetIngressClass(tctx, r.Client, r.Log, globalRule.Spec.IngressClassName) + ingressClass, err := GetIngressClass(tctx, r.Client, r.Log, globalRule.Spec.IngressClassName, r.ICGV.String()) if err != nil { r.Log.Error(err, "failed to get IngressClass") return ctrl.Result{}, err @@ -126,6 +128,12 @@ func (r *ApisixGlobalRuleReconciler) Reconcile(ctx context.Context, req ctrl.Req // SetupWithManager sets up the controller with the Manager. func (r *ApisixGlobalRuleReconciler) SetupWithManager(mgr ctrl.Manager) error { + var icWatch client.Object + if r.ICGV.String() == networkingv1beta1.SchemeGroupVersion.String() { + icWatch = &networkingv1beta1.IngressClass{} + } else { + icWatch = &networkingv1.IngressClass{} + } return ctrl.NewControllerManagedBy(mgr). For(&apiv2.ApisixGlobalRule{}, builder.WithPredicates( @@ -139,7 +147,7 @@ func (r *ApisixGlobalRuleReconciler) SetupWithManager(mgr ctrl.Manager) error { ), ). Watches( - &networkingv1.IngressClass{}, + icWatch, handler.EnqueueRequestsFromMapFunc(r.listGlobalRulesForIngressClass), builder.WithPredicates( predicate.NewPredicateFuncs(matchesIngressController), @@ -159,46 +167,12 @@ func (r *ApisixGlobalRuleReconciler) checkIngressClass(obj client.Object) bool { return false } - return r.matchesIngressClass(globalRule.Spec.IngressClassName) -} - -// matchesIngressClass checks if the given ingress class name matches our controlled classes -func (r *ApisixGlobalRuleReconciler) matchesIngressClass(ingressClassName string) bool { - if ingressClassName == "" { - // Check for default ingress class - ingressClassList := &networkingv1.IngressClassList{} - if err := r.List(context.Background(), ingressClassList, client.MatchingFields{ - indexer.IngressClass: config.GetControllerName(), - }); err != nil { - r.Log.Error(err, "failed to list ingress classes") - return false - } - - // Find the ingress class that is marked as default - for _, ic := range ingressClassList.Items { - if IsDefaultIngressClass(&ic) && matchesController(ic.Spec.Controller) { - return true - } - } - return false - } - - // Check if the specified ingress class is controlled by us - var ingressClass networkingv1.IngressClass - if err := r.Get(context.Background(), client.ObjectKey{Name: ingressClassName}, &ingressClass); err != nil { - r.Log.Error(err, "failed to get ingress class", "ingressClass", ingressClassName) - return false - } - - return matchesController(ingressClass.Spec.Controller) + return matchesIngressClass(context.Background(), r.Client, r.Log, globalRule.Spec.IngressClassName, r.ICGV.String()) } // listGlobalRulesForIngressClass list all global rules that use a specific ingress class func (r *ApisixGlobalRuleReconciler) listGlobalRulesForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request { - ingressClass, ok := obj.(*networkingv1.IngressClass) - if !ok { - return nil - } + ingressClass := convertIngressClass(obj) return ListMatchingRequests( ctx, @@ -217,7 +191,12 @@ func (r *ApisixGlobalRuleReconciler) listGlobalRulesForIngressClass(ctx context. } func (r *ApisixGlobalRuleReconciler) listGlobalRulesForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request { - return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listGlobalRulesForIngressClass) + switch r.ICGV.String() { + case networkingv1beta1.SchemeGroupVersion.String(): + return listIngressClassV1beta1RequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listGlobalRulesForIngressClass) + default: + return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listGlobalRulesForIngressClass) + } } // updateStatus updates the ApisixGlobalRule status with the given condition diff --git a/internal/controller/apisixpluginconfig_controller.go b/internal/controller/apisixpluginconfig_controller.go index 9fc7381fe..71e26c231 100644 --- a/internal/controller/apisixpluginconfig_controller.go +++ b/internal/controller/apisixpluginconfig_controller.go @@ -23,6 +23,7 @@ import ( "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -38,6 +39,8 @@ type ApisixPluginConfigReconciler struct { Scheme *runtime.Scheme Log logr.Logger Updater status.Updater + + ICGV schema.GroupVersion } // SetupWithManager sets up the controller with the Manager. diff --git a/internal/controller/apisixroute_controller.go b/internal/controller/apisixroute_controller.go index c4dde0d5a..c02048917 100644 --- a/internal/controller/apisixroute_controller.go +++ b/internal/controller/apisixroute_controller.go @@ -30,8 +30,11 @@ import ( corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" k8stypes "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -59,21 +62,36 @@ type ApisixRouteReconciler struct { Provider provider.Provider Updater status.Updater Readier readiness.ReadinessManager + + ICGV schema.GroupVersion + // supportsEndpointSlice indicates whether the cluster supports EndpointSlice API + supportsEndpointSlice bool } // SetupWithManager sets up the controller with the Manager. func (r *ApisixRouteReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). + // Check and store EndpointSlice API support + r.supportsEndpointSlice = pkgutils.HasAPIResource(mgr, &discoveryv1.EndpointSlice{}) + var icWatch client.Object + switch r.ICGV.String() { + case networkingv1beta1.SchemeGroupVersion.String(): + icWatch = &networkingv1beta1.IngressClass{} + default: + icWatch = &networkingv1.IngressClass{} + } + + bdr := ctrl.NewControllerManagedBy(mgr). For(&apiv2.ApisixRoute{}). WithEventFilter( predicate.Or( predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{}, + predicate.NewPredicateFuncs(TypePredicate[*corev1.Endpoints]()), predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()), ), ). Watches( - &networkingv1.IngressClass{}, + icWatch, handler.EnqueueRequestsFromMapFunc(r.listApisixRouteForIngressClass), builder.WithPredicates( predicate.NewPredicateFuncs(matchesIngressController), @@ -81,10 +99,21 @@ func (r *ApisixRouteReconciler) SetupWithManager(mgr ctrl.Manager) error { ). Watches(&v1alpha1.GatewayProxy{}, handler.EnqueueRequestsFromMapFunc(r.listApisixRouteForGatewayProxy), - ). - Watches(&discoveryv1.EndpointSlice{}, + ) + + // Conditionally watch EndpointSlice or Endpoints based on cluster API support + if r.supportsEndpointSlice { + bdr = bdr.Watches(&discoveryv1.EndpointSlice{}, handler.EnqueueRequestsFromMapFunc(r.listApisixRoutesForService), - ). + ) + } else { + r.Log.Info("EndpointSlice API not available, falling back to Endpoints API for service discovery") + bdr = bdr.Watches(&corev1.Endpoints{}, + handler.EnqueueRequestsFromMapFunc(r.listApisixRoutesForEndpoints), + ) + } + + return bdr. Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.listApisixRoutesForSecret), ). @@ -128,7 +157,7 @@ func (r *ApisixRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.updateStatus(&ar, err) }() - if ic, err = GetIngressClass(tctx, r.Client, r.Log, ar.Spec.IngressClassName); err != nil { + if ic, err = GetIngressClass(tctx, r.Client, r.Log, ar.Spec.IngressClassName, r.ICGV.String()); err != nil { return ctrl.Result{}, err } if err = ProcessIngressClassParameters(tctx, r.Client, r.Log, &ar, ic); err != nil { @@ -232,14 +261,20 @@ func (r *ApisixRouteReconciler) validatePluginConfig(ctx context.Context, tc *pr // Check if ApisixPluginConfig has IngressClassName and if it matches if in.Spec.IngressClassName != pc.Spec.IngressClassName && pc.Spec.IngressClassName != "" { - var pcIC networkingv1.IngressClass - if err := r.Get(ctx, client.ObjectKey{Name: pc.Spec.IngressClassName}, &pcIC); err != nil { + ic := &unstructured.Unstructured{} + ic.SetGroupVersionKind(schema.GroupVersionKind{ + Group: r.ICGV.Group, + Version: r.ICGV.Version, + Kind: types.KindIngressClass, + }) + if err := r.Get(ctx, client.ObjectKey{Name: pc.Spec.IngressClassName}, ic); err != nil { return types.ReasonError{ Reason: string(apiv2.ConditionReasonInvalidSpec), Message: fmt.Sprintf("failed to get IngressClass %s for ApisixPluginConfig %s: %v", pc.Spec.IngressClassName, pcNN, err), } } - if !matchesController(pcIC.Spec.Controller) { + controllerName, _, _ := unstructured.NestedString(ic.Object, "spec", "controller") + if !matchesController(controllerName) { return types.ReasonError{ Reason: string(apiv2.ConditionReasonInvalidSpec), Message: fmt.Sprintf("ApisixPluginConfig %s references IngressClass %s with non-matching controller", pcNN, pc.Spec.IngressClassName), @@ -341,23 +376,46 @@ func (r *ApisixRouteReconciler) validateBackends(ctx context.Context, tc *provid } tc.Services[serviceNN] = &service - var endpoints discoveryv1.EndpointSliceList - if err := r.List(ctx, &endpoints, - client.InNamespace(service.Namespace), - client.MatchingLabels{ - discoveryv1.LabelServiceName: service.Name, - }, - ); err != nil { - return types.ReasonError{ - Reason: string(apiv2.ConditionReasonInvalidSpec), - Message: fmt.Sprintf("failed to list endpoint slices: %v", err), + // Conditionally collect EndpointSlice or Endpoints based on cluster API support + if r.supportsEndpointSlice { + var endpoints discoveryv1.EndpointSliceList + if err := r.List(ctx, &endpoints, + client.InNamespace(service.Namespace), + client.MatchingLabels{ + discoveryv1.LabelServiceName: service.Name, + }, + ); err != nil { + return types.ReasonError{ + Reason: string(apiv2.ConditionReasonInvalidSpec), + Message: fmt.Sprintf("failed to list endpoint slices: %v", err), + } } - } - // backend.subset specifies a subset of upstream nodes. - // It specifies that the target pod's label should be a superset of the subset labels of the ApisixUpstream of the serviceName - subsetLabels := r.getSubsetLabels(tc, serviceNN, backend) - tc.EndpointSlices[serviceNN] = r.filterEndpointSlicesBySubsetLabels(ctx, endpoints.Items, subsetLabels) + // backend.subset specifies a subset of upstream nodes. + // It specifies that the target pod's label should be a superset of the subset labels of the ApisixUpstream of the serviceName + subsetLabels := r.getSubsetLabels(tc, serviceNN, backend) + tc.EndpointSlices[serviceNN] = r.filterEndpointSlicesBySubsetLabels(ctx, endpoints.Items, subsetLabels) + } else { + // Fallback to Endpoints API for Kubernetes 1.18 compatibility + var ep corev1.Endpoints + if err := r.Get(ctx, serviceNN, &ep); err != nil { + if client.IgnoreNotFound(err) != nil { + return types.ReasonError{ + Reason: string(apiv2.ConditionReasonInvalidSpec), + Message: fmt.Sprintf("failed to get endpoints: %v", err), + } + } + // If endpoints not found, create empty EndpointSlice list + tc.EndpointSlices[serviceNN] = []discoveryv1.EndpointSlice{} + } else { + // Convert Endpoints to EndpointSlice format for internal consistency + convertedEndpointSlices := pkgutils.ConvertEndpointsToEndpointSlice(&ep) + + // Apply subset filtering to converted EndpointSlices + subsetLabels := r.getSubsetLabels(tc, serviceNN, backend) + tc.EndpointSlices[serviceNN] = r.filterEndpointSlicesBySubsetLabels(ctx, convertedEndpointSlices, subsetLabels) + } + } } return nil @@ -443,6 +501,32 @@ func (r *ApisixRouteReconciler) listApisixRoutesForService(ctx context.Context, return pkgutils.DedupComparable(requests) } +// listApisixRoutesForEndpoints handles Endpoints objects and converts them to ApisixRoute reconcile requests. +// This function provides backward compatibility for Kubernetes 1.18 clusters that don't support EndpointSlice. +func (r *ApisixRouteReconciler) listApisixRoutesForEndpoints(ctx context.Context, obj client.Object) []reconcile.Request { + endpoint, ok := obj.(*corev1.Endpoints) + if !ok { + return nil + } + + var ( + namespace = endpoint.GetNamespace() + serviceName = endpoint.GetName() // For Endpoints, the name is the service name + arList apiv2.ApisixRouteList + ) + if err := r.List(ctx, &arList, client.MatchingFields{ + indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, serviceName), + }); err != nil { + r.Log.Error(err, "failed to list apisixroutes by service", "service", serviceName) + return nil + } + requests := make([]reconcile.Request, 0, len(arList.Items)) + for _, ar := range arList.Items { + requests = append(requests, reconcile.Request{NamespacedName: utils.NamespacedName(&ar)}) + } + return pkgutils.DedupComparable(requests) +} + func (r *ApisixRouteReconciler) listApisixRoutesForSecret(ctx context.Context, obj client.Object) []reconcile.Request { secret, ok := obj.(*corev1.Secret) if !ok { @@ -492,10 +576,7 @@ func (r *ApisixRouteReconciler) listApisixRoutesForSecret(ctx context.Context, o } func (r *ApisixRouteReconciler) listApisixRouteForIngressClass(ctx context.Context, object client.Object) (requests []reconcile.Request) { - ingressClass, ok := object.(*networkingv1.IngressClass) - if !ok { - return nil - } + ingressClass := convertIngressClass(object) return ListMatchingRequests( ctx, @@ -513,8 +594,13 @@ func (r *ApisixRouteReconciler) listApisixRouteForIngressClass(ctx context.Conte ) } -func (r *ApisixRouteReconciler) listApisixRouteForGatewayProxy(ctx context.Context, object client.Object) (requests []reconcile.Request) { - return listIngressClassRequestsForGatewayProxy(ctx, r.Client, object, r.Log, r.listApisixRouteForIngressClass) +func (r *ApisixRouteReconciler) listApisixRouteForGatewayProxy(ctx context.Context, obj client.Object) (requests []reconcile.Request) { + switch r.ICGV.String() { + case networkingv1beta1.SchemeGroupVersion.String(): + return listIngressClassV1beta1RequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listApisixRouteForIngressClass) + default: + return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listApisixRouteForIngressClass) + } } func (r *ApisixRouteReconciler) listApisixRouteForApisixUpstream(ctx context.Context, object client.Object) (requests []reconcile.Request) { @@ -556,13 +642,20 @@ func (r *ApisixRouteReconciler) listApisixRoutesForPluginConfig(ctx context.Cont // First check if the ApisixPluginConfig has matching IngressClassName if pc.Spec.IngressClassName != "" { - var ic networkingv1.IngressClass - if err := r.Get(ctx, client.ObjectKey{Name: pc.Spec.IngressClassName}, &ic); err != nil { + var icObj client.Object + switch r.ICGV.String() { + case networkingv1beta1.SchemeGroupVersion.String(): + icObj = &networkingv1beta1.IngressClass{} + default: + icObj = &networkingv1.IngressClass{} + } + if err := r.Get(ctx, client.ObjectKey{Name: pc.Spec.IngressClassName}, icObj); err != nil { if client.IgnoreNotFound(err) != nil { r.Log.Error(err, "failed to get IngressClass for ApisixPluginConfig", "pluginconfig", pc.Name) } return nil } + ic := convertIngressClass(icObj) if !matchesController(ic.Spec.Controller) { return nil } diff --git a/internal/controller/apisixtls_controller.go b/internal/controller/apisixtls_controller.go index 7410fdb07..762d96343 100644 --- a/internal/controller/apisixtls_controller.go +++ b/internal/controller/apisixtls_controller.go @@ -24,8 +24,10 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -36,7 +38,6 @@ import ( "github.com/apache/apisix-ingress-controller/api/v1alpha1" apiv2 "github.com/apache/apisix-ingress-controller/api/v2" - "github.com/apache/apisix-ingress-controller/internal/controller/config" "github.com/apache/apisix-ingress-controller/internal/controller/indexer" "github.com/apache/apisix-ingress-controller/internal/controller/status" "github.com/apache/apisix-ingress-controller/internal/manager/readiness" @@ -52,10 +53,19 @@ type ApisixTlsReconciler struct { Provider provider.Provider Updater status.Updater Readier readiness.ReadinessManager + + ICGV schema.GroupVersion } // SetupWithManager sets up the controller with the Manager. func (r *ApisixTlsReconciler) SetupWithManager(mgr ctrl.Manager) error { + var icWatch client.Object + switch r.ICGV.String() { + case networkingv1beta1.SchemeGroupVersion.String(): + icWatch = &networkingv1beta1.IngressClass{} + default: + icWatch = &networkingv1.IngressClass{} + } return ctrl.NewControllerManagedBy(mgr). For(&apiv2.ApisixTls{}, builder.WithPredicates( @@ -70,7 +80,7 @@ func (r *ApisixTlsReconciler) SetupWithManager(mgr ctrl.Manager) error { ), ). Watches( - &networkingv1.IngressClass{}, + icWatch, handler.EnqueueRequestsFromMapFunc(r.listApisixTlsForIngressClass), builder.WithPredicates( predicate.NewPredicateFuncs(matchesIngressController), @@ -115,7 +125,7 @@ func (r *ApisixTlsReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( tctx := provider.NewDefaultTranslateContext(ctx) // get the ingress class - ingressClass, err := GetIngressClass(tctx, r.Client, r.Log, tls.Spec.IngressClassName) + ingressClass, err := GetIngressClass(tctx, r.Client, r.Log, tls.Spec.IngressClassName, r.ICGV.String()) if err != nil { r.Log.Error(err, "failed to get IngressClass") r.updateStatus(&tls, metav1.Condition{ @@ -240,38 +250,7 @@ func (r *ApisixTlsReconciler) checkIngressClass(obj client.Object) bool { return false } - return r.matchesIngressClass(tls.Spec.IngressClassName) -} - -// matchesIngressClass checks if the given ingress class name matches our controlled classes -func (r *ApisixTlsReconciler) matchesIngressClass(ingressClassName string) bool { - if ingressClassName == "" { - // Check for default ingress class - ingressClassList := &networkingv1.IngressClassList{} - if err := r.List(context.Background(), ingressClassList, client.MatchingFields{ - indexer.IngressClass: config.GetControllerName(), - }); err != nil { - r.Log.Error(err, "failed to list ingress classes") - return false - } - - // Find the ingress class that is marked as default - for _, ic := range ingressClassList.Items { - if IsDefaultIngressClass(&ic) && matchesController(ic.Spec.Controller) { - return true - } - } - return false - } - - // Check if the specified ingress class is controlled by us - var ingressClass networkingv1.IngressClass - if err := r.Get(context.Background(), client.ObjectKey{Name: ingressClassName}, &ingressClass); err != nil { - r.Log.Error(err, "failed to get ingress class", "ingressClass", ingressClassName) - return false - } - - return matchesController(ingressClass.Spec.Controller) + return matchesIngressClass(context.Background(), r.Client, r.Log, tls.Spec.IngressClassName, r.ICGV.String()) } func (r *ApisixTlsReconciler) listApisixTlsForSecret(ctx context.Context, obj client.Object) []reconcile.Request { @@ -293,10 +272,7 @@ func (r *ApisixTlsReconciler) listApisixTlsForSecret(ctx context.Context, obj cl // listApisixTlsForIngressClass list all TLS that use a specific ingress class func (r *ApisixTlsReconciler) listApisixTlsForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request { - ingressClass, ok := obj.(*networkingv1.IngressClass) - if !ok { - return nil - } + ingressClass := convertIngressClass(obj) return ListMatchingRequests( ctx, @@ -316,5 +292,10 @@ func (r *ApisixTlsReconciler) listApisixTlsForIngressClass(ctx context.Context, // listApisixTlsForGatewayProxy list all TLS that use a specific gateway proxy func (r *ApisixTlsReconciler) listApisixTlsForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request { - return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listApisixTlsForIngressClass) + switch r.ICGV.String() { + case networkingv1beta1.SchemeGroupVersion.String(): + return listIngressClassV1beta1RequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listApisixTlsForIngressClass) + default: + return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listApisixTlsForIngressClass) + } } diff --git a/internal/controller/apisixupstream_controller.go b/internal/controller/apisixupstream_controller.go index 2c4598896..f8655055e 100644 --- a/internal/controller/apisixupstream_controller.go +++ b/internal/controller/apisixupstream_controller.go @@ -22,6 +22,7 @@ import ( "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -37,6 +38,8 @@ type ApisixUpstreamReconciler struct { Scheme *runtime.Scheme Log logr.Logger Updater status.Updater + + ICGV schema.GroupVersion } // SetupWithManager sets up the controller with the Manager. diff --git a/internal/controller/gatewayproxy_controller.go b/internal/controller/gatewayproxy_controller.go index 43ba32f74..4932a893d 100644 --- a/internal/controller/gatewayproxy_controller.go +++ b/internal/controller/gatewayproxy_controller.go @@ -25,8 +25,11 @@ import ( corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + k8stypes "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -38,6 +41,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/controller/indexer" "github.com/apache/apisix-ingress-controller/internal/provider" "github.com/apache/apisix-ingress-controller/internal/utils" + pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils" ) // GatewayProxyController reconciles a GatewayProxy object. @@ -47,23 +51,44 @@ type GatewayProxyController struct { Scheme *runtime.Scheme Log logr.Logger Provider provider.Provider + + ICGV schema.GroupVersion + // supportsEndpointSlice indicates whether the cluster supports EndpointSlice API + supportsEndpointSlice bool + supportsGateway bool } func (r *GatewayProxyController) SetupWithManager(mrg ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mrg). + // Check and store EndpointSlice API support + r.supportsEndpointSlice = pkgutils.HasAPIResource(mrg, &discoveryv1.EndpointSlice{}) + r.supportsGateway = pkgutils.HasAPIResource(mrg, &gatewayv1.Gateway{}) + + bdr := ctrl.NewControllerManagedBy(mrg). For(&v1alpha1.GatewayProxy{}). WithEventFilter( predicate.Or( predicate.GenerationChangedPredicate{}, + predicate.NewPredicateFuncs(TypePredicate[*corev1.Endpoints]()), predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()), ), ). Watches(&corev1.Service{}, handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderService), - ). - Watches(&discoveryv1.EndpointSlice{}, + ) + + // Conditionally watch EndpointSlice or Endpoints based on cluster API support + if r.supportsEndpointSlice { + bdr = bdr.Watches(&discoveryv1.EndpointSlice{}, handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderEndpointSlice), - ). + ) + } else { + r.Log.Info("EndpointSlice API not available, falling back to Endpoints API for provider service discovery") + bdr = bdr.Watches(&corev1.Endpoints{}, + handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderEndpoints), + ) + } + + return bdr. Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForSecret), ). @@ -93,10 +118,10 @@ func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request if providerService == nil { tctx.EndpointSlices[req.NamespacedName] = nil } else { - if err := addProviderEndpointsToTranslateContext(tctx, r.Client, types.NamespacedName{ + if err := addProviderEndpointsToTranslateContextWithEndpointSliceSupport(tctx, r.Client, types.NamespacedName{ Namespace: gp.Namespace, Name: providerService.Name, - }); err != nil { + }, r.supportsEndpointSlice); err != nil { return reconcile.Result{}, err } } @@ -106,7 +131,7 @@ func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request if auth.AdminKey != nil && auth.AdminKey.ValueFrom != nil && auth.AdminKey.ValueFrom.SecretKeyRef != nil { var ( secret corev1.Secret - secretNN = types.NamespacedName{ + secretNN = k8stypes.NamespacedName{ Namespace: gp.GetNamespace(), Name: auth.AdminKey.ValueFrom.SecretKeyRef.Name, } @@ -118,29 +143,44 @@ func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request tctx.Secrets[secretNN] = &secret } + indexKey := indexer.GenIndexKey(gp.GetNamespace(), gp.GetName()) + // list Gateways that reference the GatewayProxy - var ( - gatewayList gatewayv1.GatewayList - ingressClassList networkingv1.IngressClassList - indexKey = indexer.GenIndexKey(gp.GetNamespace(), gp.GetName()) - ) - if err := r.List(ctx, &gatewayList, client.MatchingFields{indexer.ParametersRef: indexKey}); err != nil { - r.Log.Error(err, "failed to list GatewayList") - return ctrl.Result{}, nil + if r.supportsGateway { + var gatewayList gatewayv1.GatewayList + if err := r.List(ctx, &gatewayList, client.MatchingFields{indexer.ParametersRef: indexKey}); err != nil { + r.Log.Error(err, "failed to list GatewayList") + return ctrl.Result{}, nil + } + // append referrers to translate context + for _, item := range gatewayList.Items { + tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item)) + } } - // list IngressClasses that reference the GatewayProxy - if err := r.List(ctx, &ingressClassList, client.MatchingFields{indexer.IngressClassParametersRef: indexKey}); err != nil { - r.Log.Error(err, "failed to list IngressClassList") - return reconcile.Result{}, err - } + switch r.ICGV.String() { + case networkingv1.SchemeGroupVersion.String(), "": + var ingressClassList networkingv1.IngressClassList + // list IngressClasses that reference the GatewayProxy + if err := r.List(ctx, &ingressClassList, client.MatchingFields{indexer.IngressClassParametersRef: indexKey}); err != nil { + r.Log.Error(err, "failed to list IngressClassList") + return reconcile.Result{}, err + } - // append referrers to translate context - for _, item := range gatewayList.Items { - tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item)) - } - for _, item := range ingressClassList.Items { - tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item)) + for _, item := range ingressClassList.Items { + tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item)) + } + case networkingv1beta1.SchemeGroupVersion.String(): + var ingressClassList networkingv1beta1.IngressClassList + // list IngressClasses that reference the GatewayProxy + if err := r.List(ctx, &ingressClassList, client.MatchingFields{indexer.IngressClassParametersRef: indexKey}); err != nil { + r.Log.Error(err, "failed to list IngressClassList") + return reconcile.Result{}, err + } + + for _, item := range ingressClassList.Items { + tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item)) + } } if err := r.Provider.Update(ctx, tctx, &gp); err != nil { @@ -174,6 +214,20 @@ func (r *GatewayProxyController) listGatewayProxiesForProviderEndpointSlice(ctx }) } +// listGatewayProxiesForProviderEndpoints handles Endpoints objects and converts them to GatewayProxy reconcile requests. +// This function provides backward compatibility for Kubernetes 1.18 clusters that don't support EndpointSlice. +func (r *GatewayProxyController) listGatewayProxiesForProviderEndpoints(ctx context.Context, obj client.Object) (requests []reconcile.Request) { + endpoint, ok := obj.(*corev1.Endpoints) + if !ok { + r.Log.Error(errors.New("unexpected object type"), "failed to convert object to Endpoints") + return nil + } + + return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{ + indexer.ServiceIndexRef: indexer.GenIndexKey(endpoint.GetNamespace(), endpoint.GetName()), + }) +} + func (r *GatewayProxyController) listGatewayProxiesForSecret(ctx context.Context, object client.Object) []reconcile.Request { secret, ok := object.(*corev1.Secret) if !ok { diff --git a/internal/controller/httproute_controller.go b/internal/controller/httproute_controller.go index b6f392878..c7cec3051 100644 --- a/internal/controller/httproute_controller.go +++ b/internal/controller/httproute_controller.go @@ -52,6 +52,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/provider" "github.com/apache/apisix-ingress-controller/internal/types" "github.com/apache/apisix-ingress-controller/internal/utils" + pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils" ) // HTTPRouteReconciler reconciles a GatewayClass object. @@ -67,18 +68,39 @@ type HTTPRouteReconciler struct { //nolint:revive Updater status.Updater Readier readiness.ReadinessManager + + // supportsEndpointSlice indicates whether the cluster supports EndpointSlice API + supportsEndpointSlice bool } // SetupWithManager sets up the controller with the Manager. func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error { r.genericEvent = make(chan event.GenericEvent, 100) + // Check and store EndpointSlice API support + r.supportsEndpointSlice = pkgutils.HasAPIResource(mgr, &discoveryv1.EndpointSlice{}) + bdr := ctrl.NewControllerManagedBy(mgr). For(&gatewayv1.HTTPRoute{}). - WithEventFilter(predicate.GenerationChangedPredicate{}). - Watches(&discoveryv1.EndpointSlice{}, + WithEventFilter( + predicate.Or( + predicate.GenerationChangedPredicate{}, + predicate.NewPredicateFuncs(TypePredicate[*corev1.Endpoints]()), + )) + + // Conditionally watch EndpointSlice or Endpoints based on cluster API support + if r.supportsEndpointSlice { + bdr = bdr.Watches(&discoveryv1.EndpointSlice{}, handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByServiceBef), - ). + ) + } else { + r.Log.Info("EndpointSlice API not available, falling back to Endpoints API for service discovery") + bdr = bdr.Watches(&corev1.Endpoints{}, + handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByServiceForEndpoints), + ) + } + + bdr = bdr. Watches(&v1alpha1.PluginConfig{}, handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByExtensionRef), ). @@ -288,6 +310,36 @@ func (r *HTTPRouteReconciler) listHTTPRoutesByServiceBef(ctx context.Context, ob return requests } +// listHTTPRoutesByServiceForEndpoints handles Endpoints objects and converts them to HTTPRoute reconcile requests. +// This function provides backward compatibility for Kubernetes 1.18 clusters that don't support EndpointSlice. +func (r *HTTPRouteReconciler) listHTTPRoutesByServiceForEndpoints(ctx context.Context, obj client.Object) []reconcile.Request { + endpoint, ok := obj.(*corev1.Endpoints) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to Endpoints") + return nil + } + namespace := endpoint.GetNamespace() + serviceName := endpoint.GetName() // For Endpoints, the name is the service name + + hrList := &gatewayv1.HTTPRouteList{} + if err := r.List(ctx, hrList, client.MatchingFields{ + indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, serviceName), + }); err != nil { + r.Log.Error(err, "failed to list httproutes by service", "service", serviceName) + return nil + } + requests := make([]reconcile.Request, 0, len(hrList.Items)) + for _, hr := range hrList.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Namespace: hr.Namespace, + Name: hr.Name, + }, + }) + } + return requests +} + func (r *HTTPRouteReconciler) listHTTPRoutesByExtensionRef(ctx context.Context, obj client.Object) []reconcile.Request { pluginconfig, ok := obj.(*v1alpha1.PluginConfig) if !ok { @@ -520,19 +572,37 @@ func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.Transla } tctx.Services[targetNN] = &service - endpointSliceList := new(discoveryv1.EndpointSliceList) - if err := r.List(tctx, endpointSliceList, - client.InNamespace(targetNN.Namespace), - client.MatchingLabels{ - discoveryv1.LabelServiceName: targetNN.Name, - }, - ); err != nil { - r.Log.Error(err, "failed to list endpoint slices", "Service", targetNN) - terr = err - continue + // Conditionally collect EndpointSlice or Endpoints based on cluster API support + if r.supportsEndpointSlice { + endpointSliceList := new(discoveryv1.EndpointSliceList) + if err := r.List(tctx, endpointSliceList, + client.InNamespace(targetNN.Namespace), + client.MatchingLabels{ + discoveryv1.LabelServiceName: targetNN.Name, + }, + ); err != nil { + r.Log.Error(err, "failed to list endpoint slices", "Service", targetNN) + terr = err + continue + } + tctx.EndpointSlices[targetNN] = endpointSliceList.Items + } else { + // Fallback to Endpoints API for Kubernetes 1.18 compatibility + var endpoints corev1.Endpoints + if err := r.Get(tctx, targetNN, &endpoints); err != nil { + if client.IgnoreNotFound(err) != nil { + r.Log.Error(err, "failed to get endpoints", "Service", targetNN) + terr = err + continue + } + // If endpoints not found, create empty EndpointSlice list + tctx.EndpointSlices[targetNN] = []discoveryv1.EndpointSlice{} + } else { + // Convert Endpoints to EndpointSlice format for internal consistency + convertedEndpointSlices := pkgutils.ConvertEndpointsToEndpointSlice(&endpoints) + tctx.EndpointSlices[targetNN] = convertedEndpointSlices + } } - - tctx.EndpointSlices[targetNN] = endpointSliceList.Items } return terr } diff --git a/internal/controller/indexer/indexer.go b/internal/controller/indexer/indexer.go index c371d6091..00de3a7a4 100644 --- a/internal/controller/indexer/indexer.go +++ b/internal/controller/indexer/indexer.go @@ -23,6 +23,7 @@ import ( corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" @@ -56,10 +57,14 @@ func SetupIndexer(mgr ctrl.Manager) error { // Gateway API indexers - conditional setup based on API availability for resource, setup := range map[client.Object]func(ctrl.Manager) error{ - &gatewayv1.Gateway{}: setupGatewayIndexer, - &gatewayv1.HTTPRoute{}: setupHTTPRouteIndexer, - &gatewayv1.GatewayClass{}: setupGatewayClassIndexer, - &v1alpha1.Consumer{}: setupConsumerIndexer, + &gatewayv1.Gateway{}: setupGatewayIndexer, + &gatewayv1.HTTPRoute{}: setupHTTPRouteIndexer, + &gatewayv1.GatewayClass{}: setupGatewayClassIndexer, + &v1alpha1.Consumer{}: setupConsumerIndexer, + &networkingv1.Ingress{}: setupIngressIndexer, + &networkingv1.IngressClass{}: setupIngressClassIndexer, + &networkingv1beta1.IngressClass{}: setupIngressClassV1beta1Indexer, + &v1alpha1.BackendTrafficPolicy{}: setupBackendTrafficPolicyIndexer, } { if utils.HasAPIResource(mgr, resource) { if err := setup(mgr); err != nil { @@ -81,9 +86,6 @@ func SetupIndexer(mgr ctrl.Manager) error { // Core Kubernetes and APISIX indexers - always setup these for _, setup := range []func(ctrl.Manager) error{ - setupIngressIndexer, - setupBackendTrafficPolicyIndexer, - setupIngressClassIndexer, setupGatewayProxyIndexer, setupApisixRouteIndexer, setupApisixPluginConfigIndexer, @@ -276,6 +278,30 @@ func setupIngressClassIndexer(mgr ctrl.Manager) error { return nil } +func setupIngressClassV1beta1Indexer(mgr ctrl.Manager) error { + // create IngressClass index + if err := mgr.GetFieldIndexer().IndexField( + context.Background(), + &networkingv1beta1.IngressClass{}, + IngressClass, + IngressClassV1beta1IndexFunc, + ); err != nil { + return err + } + + // create IngressClassParametersRef index + if err := mgr.GetFieldIndexer().IndexField( + context.Background(), + &networkingv1beta1.IngressClass{}, + IngressClassParametersRef, + IngressClassV1beta1ParametersRefIndexFunc, + ); err != nil { + return err + } + + return nil +} + func setupGatewayProxyIndexer(mgr ctrl.Manager) error { if err := mgr.GetFieldIndexer().IndexField( context.Background(), @@ -395,6 +421,15 @@ func setupBackendTrafficPolicyIndexer(mgr ctrl.Manager) error { return nil } +func IngressClassV1beta1IndexFunc(rawObj client.Object) []string { + ingressClass := rawObj.(*networkingv1beta1.IngressClass) + if ingressClass.Spec.Controller == "" { + return nil + } + controllerName := ingressClass.Spec.Controller + return []string{controllerName} +} + func IngressClassIndexFunc(rawObj client.Object) []string { ingressClass := rawObj.(*networkingv1.IngressClass) if ingressClass.Spec.Controller == "" { @@ -710,6 +745,22 @@ func IngressClassParametersRefIndexFunc(rawObj client.Object) []string { return nil } +func IngressClassV1beta1ParametersRefIndexFunc(rawObj client.Object) []string { + ingressClass := rawObj.(*networkingv1beta1.IngressClass) + // check if the IngressClass references this gateway proxy + if ingressClass.Spec.Parameters != nil && + ingressClass.Spec.Parameters.APIGroup != nil && + *ingressClass.Spec.Parameters.APIGroup == v1alpha1.GroupVersion.Group && + ingressClass.Spec.Parameters.Kind == "GatewayProxy" { + ns := ingressClass.GetNamespace() + if ingressClass.Spec.Parameters.Namespace != nil { + ns = *ingressClass.Spec.Parameters.Namespace + } + return []string{GenIndexKey(ns, ingressClass.Spec.Parameters.Name)} + } + return nil +} + func ApisixPluginConfigSecretIndexFunc(obj client.Object) (keys []string) { pc := obj.(*apiv2.ApisixPluginConfig) for _, plugin := range pc.Spec.Plugins { diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index 25b52990c..66167a569 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -48,6 +48,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/manager/readiness" "github.com/apache/apisix-ingress-controller/internal/provider" "github.com/apache/apisix-ingress-controller/internal/utils" + pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils" ) // IngressReconciler reconciles a Ingress object. @@ -61,13 +62,19 @@ type IngressReconciler struct { //nolint:revive Updater status.Updater Readier readiness.ReadinessManager + + // supportsEndpointSlice indicates whether the cluster supports EndpointSlice API + supportsEndpointSlice bool } // SetupWithManager sets up the controller with the Manager. func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error { r.genericEvent = make(chan event.GenericEvent, 100) - return ctrl.NewControllerManagedBy(mgr). + // Check and store EndpointSlice API support + r.supportsEndpointSlice = pkgutils.HasAPIResource(mgr, &discoveryv1.EndpointSlice{}) + + bdr := ctrl.NewControllerManagedBy(mgr). For(&networkingv1.Ingress{}, builder.WithPredicates( predicate.NewPredicateFuncs(r.checkIngressClass), @@ -77,6 +84,7 @@ func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error { predicate.Or( predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{}, + predicate.NewPredicateFuncs(TypePredicate[*corev1.Endpoints]()), predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()), ), ). @@ -86,11 +94,23 @@ func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error { builder.WithPredicates( predicate.NewPredicateFuncs(r.matchesIngressController), ), - ). - Watches( + ) + + // Conditionally watch EndpointSlice or Endpoints based on cluster API support + if r.supportsEndpointSlice { + bdr = bdr.Watches( &discoveryv1.EndpointSlice{}, handler.EnqueueRequestsFromMapFunc(r.listIngressesByService), - ). + ) + } else { + r.Log.Info("EndpointSlice API not available, falling back to Endpoints API for service discovery") + bdr = bdr.Watches( + &corev1.Endpoints{}, + handler.EnqueueRequestsFromMapFunc(r.listIngressesByEndpoints), + ) + } + + return bdr. Watches( &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.listIngressesBySecret), @@ -214,7 +234,7 @@ func (r *IngressReconciler) getIngressClass(ctx context.Context, obj client.Obje if ingress.Spec.IngressClassName != nil { ingressClassName = *ingress.Spec.IngressClassName } - return GetIngressClass(ctx, r.Client, r.Log, ingressClassName) + return GetIngressClass(ctx, r.Client, r.Log, ingressClassName, "") } // checkIngressClass check if the ingress uses the ingress class that we control @@ -319,6 +339,40 @@ func (r *IngressReconciler) listIngressesByService(ctx context.Context, obj clie return requests } +// listIngressesByEndpoints handles Endpoints objects and converts them to Ingress reconcile requests. +// This function provides backward compatibility for Kubernetes 1.18 clusters that don't support EndpointSlice. +func (r *IngressReconciler) listIngressesByEndpoints(ctx context.Context, obj client.Object) []reconcile.Request { + endpoint, ok := obj.(*corev1.Endpoints) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to Endpoints") + return nil + } + + namespace := endpoint.GetNamespace() + serviceName := endpoint.GetName() // For Endpoints, the name is the service name + + ingressList := &networkingv1.IngressList{} + if err := r.List(ctx, ingressList, client.MatchingFields{ + indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, serviceName), + }); err != nil { + r.Log.Error(err, "failed to list ingresses by service", "service", serviceName) + return nil + } + + requests := make([]reconcile.Request, 0, len(ingressList.Items)) + for _, ingress := range ingressList.Items { + if r.checkIngressClass(&ingress) { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Namespace: ingress.Namespace, + Name: ingress.Name, + }, + }) + } + } + return requests +} + // listIngressesBySecret list all ingresses that use a specific secret func (r *IngressReconciler) listIngressesBySecret(ctx context.Context, obj client.Object) []reconcile.Request { secret, ok := obj.(*corev1.Secret) @@ -557,20 +611,38 @@ func (r *IngressReconciler) processBackendService(tctx *provider.TranslateContex return err } - // get the endpoint slices - endpointSliceList := &discoveryv1.EndpointSliceList{} - if err := r.List(tctx, endpointSliceList, - client.InNamespace(namespace), - client.MatchingLabels{ - discoveryv1.LabelServiceName: backendService.Name, - }, - ); err != nil { - r.Log.Error(err, "failed to list endpoint slices", "namespace", namespace, "name", backendService.Name) - return err - } + // Conditionally get EndpointSlice or Endpoints based on cluster API support + if r.supportsEndpointSlice { + // get the endpoint slices + endpointSliceList := &discoveryv1.EndpointSliceList{} + if err := r.List(tctx, endpointSliceList, + client.InNamespace(namespace), + client.MatchingLabels{ + discoveryv1.LabelServiceName: backendService.Name, + }, + ); err != nil { + r.Log.Error(err, "failed to list endpoint slices", "namespace", namespace, "name", backendService.Name) + return err + } - // save the endpoint slices to the translate context - tctx.EndpointSlices[serviceNS] = endpointSliceList.Items + // save the endpoint slices to the translate context + tctx.EndpointSlices[serviceNS] = endpointSliceList.Items + } else { + // Fallback to Endpoints API for Kubernetes 1.18 compatibility + var endpoints corev1.Endpoints + if err := r.Get(tctx, serviceNS, &endpoints); err != nil { + if client.IgnoreNotFound(err) != nil { + r.Log.Error(err, "failed to get endpoints", "namespace", namespace, "name", backendService.Name) + return err + } + // If endpoints not found, create empty EndpointSlice list + tctx.EndpointSlices[serviceNS] = []discoveryv1.EndpointSlice{} + } else { + // Convert Endpoints to EndpointSlice format for internal consistency + convertedEndpointSlices := pkgutils.ConvertEndpointsToEndpointSlice(&endpoints) + tctx.EndpointSlices[serviceNS] = convertedEndpointSlices + } + } tctx.Services[serviceNS] = &service return nil } diff --git a/internal/controller/ingressclass_v1beta1_controller.go b/internal/controller/ingressclass_v1beta1_controller.go new file mode 100644 index 000000000..78a59cdcd --- /dev/null +++ b/internal/controller/ingressclass_v1beta1_controller.go @@ -0,0 +1,245 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package controller + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + v1beta1 "k8s.io/api/networking/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/apache/apisix-ingress-controller/api/v1alpha1" + "github.com/apache/apisix-ingress-controller/internal/controller/indexer" + "github.com/apache/apisix-ingress-controller/internal/provider" + "github.com/apache/apisix-ingress-controller/internal/utils" +) + +// IngressClassV1beta1Reconciler reconciles a IngressClassV1beta1 object. +type IngressClassV1beta1Reconciler struct { + client.Client + Scheme *runtime.Scheme + Log logr.Logger + + Provider provider.Provider +} + +// SetupWithManager sets up the controller with the Manager. +func (r *IngressClassV1beta1Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For( + &v1beta1.IngressClass{}, + builder.WithPredicates( + predicate.NewPredicateFuncs(r.matchesController), + ), + ). + WithEventFilter( + predicate.Or( + predicate.GenerationChangedPredicate{}, + predicate.AnnotationChangedPredicate{}, + predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()), + ), + ). + Watches( + &v1alpha1.GatewayProxy{}, + handler.EnqueueRequestsFromMapFunc(r.listIngressClassV1beta1esForGatewayProxy), + ). + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.listIngressClassV1beta1esForSecret), + ). + Complete(r) +} + +func (r *IngressClassV1beta1Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + IngressClassV1beta1 := new(v1beta1.IngressClass) + if err := r.Get(ctx, req.NamespacedName, IngressClassV1beta1); err != nil { + if client.IgnoreNotFound(err) == nil { + IngressClassV1beta1.Name = req.Name + + IngressClassV1beta1.TypeMeta = metav1.TypeMeta{ + Kind: KindIngressClass, + APIVersion: v1beta1.SchemeGroupVersion.String(), + } + + if err := r.Provider.Delete(ctx, IngressClassV1beta1); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + // Create a translate context + tctx := provider.NewDefaultTranslateContext(ctx) + + if err := r.processInfrastructure(tctx, IngressClassV1beta1); err != nil { + r.Log.Error(err, "failed to process infrastructure for IngressClassV1beta1", "IngressClassV1beta1", IngressClassV1beta1.GetName()) + return ctrl.Result{}, err + } + + if err := r.Provider.Update(ctx, tctx, IngressClassV1beta1); err != nil { + r.Log.Error(err, "failed to update IngressClassV1beta1", "IngressClassV1beta1", IngressClassV1beta1.GetName()) + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +func (r *IngressClassV1beta1Reconciler) matchesController(obj client.Object) bool { + IngressClassV1beta1, ok := obj.(*v1beta1.IngressClass) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to IngressClassV1beta1") + return false + } + return matchesController(IngressClassV1beta1.Spec.Controller) +} + +func (r *IngressClassV1beta1Reconciler) listIngressClassV1beta1esForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request { + gatewayProxy, ok := obj.(*v1alpha1.GatewayProxy) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to GatewayProxy") + return nil + } + namespace := gatewayProxy.GetNamespace() + name := gatewayProxy.GetName() + + IngressClassV1beta1List := &v1beta1.IngressClassList{} + if err := r.List(ctx, IngressClassV1beta1List, client.MatchingFields{ + indexer.IngressClassParametersRef: indexer.GenIndexKey(namespace, name), + }); err != nil { + r.Log.Error(err, "failed to list ingress classes for gateway proxy", "gatewayproxy", gatewayProxy.GetName()) + return nil + } + + recs := make([]reconcile.Request, 0, len(IngressClassV1beta1List.Items)) + for _, IngressClassV1beta1 := range IngressClassV1beta1List.Items { + if !r.matchesController(&IngressClassV1beta1) { + continue + } + recs = append(recs, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Name: IngressClassV1beta1.GetName(), + }, + }) + } + return recs +} + +func (r *IngressClassV1beta1Reconciler) listIngressClassV1beta1esForSecret(ctx context.Context, obj client.Object) []reconcile.Request { + secret, ok := obj.(*corev1.Secret) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to Secret") + return nil + } + + // 1. list gateway proxies by secret + gatewayProxyList := &v1alpha1.GatewayProxyList{} + if err := r.List(ctx, gatewayProxyList, client.MatchingFields{ + indexer.SecretIndexRef: indexer.GenIndexKey(secret.GetNamespace(), secret.GetName()), + }); err != nil { + r.Log.Error(err, "failed to list gateway proxies by secret", "secret", secret.GetName()) + return nil + } + + // 2. list ingress classes by gateway proxies + requests := make([]reconcile.Request, 0) + for _, gatewayProxy := range gatewayProxyList.Items { + requests = append(requests, r.listIngressClassV1beta1esForGatewayProxy(ctx, &gatewayProxy)...) + } + + return distinctRequests(requests) +} + +func (r *IngressClassV1beta1Reconciler) processInfrastructure(tctx *provider.TranslateContext, IngressClassV1beta1 *v1beta1.IngressClass) error { + if IngressClassV1beta1.Spec.Parameters == nil { + return nil + } + + if IngressClassV1beta1.Spec.Parameters.APIGroup == nil || + *IngressClassV1beta1.Spec.Parameters.APIGroup != v1alpha1.GroupVersion.Group || + IngressClassV1beta1.Spec.Parameters.Kind != KindGatewayProxy { + return nil + } + + namespace := "default" + if IngressClassV1beta1.Spec.Parameters.Namespace != nil { + namespace = *IngressClassV1beta1.Spec.Parameters.Namespace + } + + gatewayProxy := new(v1alpha1.GatewayProxy) + if err := r.Get(context.Background(), client.ObjectKey{ + Namespace: namespace, + Name: IngressClassV1beta1.Spec.Parameters.Name, + }, gatewayProxy); err != nil { + return fmt.Errorf("failed to get gateway proxy: %w", err) + } + + rk := utils.NamespacedNameKind(IngressClassV1beta1) + + tctx.GatewayProxies[rk] = *gatewayProxy + tctx.ResourceParentRefs[rk] = append(tctx.ResourceParentRefs[rk], rk) + + // Load secrets if needed + if gatewayProxy.Spec.Provider != nil && gatewayProxy.Spec.Provider.ControlPlane != nil { + auth := gatewayProxy.Spec.Provider.ControlPlane.Auth + if auth.Type == v1alpha1.AuthTypeAdminKey && auth.AdminKey != nil && auth.AdminKey.ValueFrom != nil { + if auth.AdminKey.ValueFrom.SecretKeyRef != nil { + secretRef := auth.AdminKey.ValueFrom.SecretKeyRef + secret := &corev1.Secret{} + if err := r.Get(context.Background(), client.ObjectKey{ + Namespace: namespace, + Name: secretRef.Name, + }, secret); err != nil { + r.Log.Error(err, "failed to get secret for gateway proxy", "namespace", namespace, "name", secretRef.Name) + return err + } + tctx.Secrets[client.ObjectKey{ + Namespace: namespace, + Name: secretRef.Name, + }] = secret + } + } + } + + if service := gatewayProxy.Spec.Provider.ControlPlane.Service; service != nil { + if err := addProviderEndpointsToTranslateContext(tctx, r.Client, types.NamespacedName{ + Namespace: gatewayProxy.GetNamespace(), + Name: service.Name, + }); err != nil { + return err + } + } + + _, ok := tctx.GatewayProxies[rk] + if !ok { + return fmt.Errorf("no gateway proxy found for ingress class") + } + + return nil +} diff --git a/internal/controller/lister/ingressclass_lister.go b/internal/controller/lister/ingressclass_lister.go new file mode 100644 index 000000000..99e14498f --- /dev/null +++ b/internal/controller/lister/ingressclass_lister.go @@ -0,0 +1,95 @@ +package lister + +import ( + "context" + + networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + + types "github.com/apache/apisix-ingress-controller/internal/types" +) + +// GenericIngressClass 是对 v1 和 v1beta1 的通用抽象 +type IngressClass struct { + Name string + Controller string + Raw client.Object +} + +// IngressClassLister 定义统一的接口 +type IngressClassLister interface { + List(ctx context.Context) ([]*types.IngressClass, error) + Get(ctx context.Context, name string) (*types.IngressClass, error) +} + +type ingressClassV1Lister struct { + client client.Client +} + +func (l *ingressClassV1Lister) List(ctx context.Context) ([]*types.IngressClass, error) { + var list networkingv1.IngressClassList + if err := l.client.List(ctx, &list); err != nil { + return nil, err + } + + var result []*types.IngressClass + for _, item := range list.Items { + obj := item // copy to avoid pointer issue + result = append(result, &types.IngressClass{ + Object: &obj, + }) + } + return result, nil +} + +func (l *ingressClassV1Lister) Get(ctx context.Context, name string) (*types.IngressClass, error) { + var obj networkingv1.IngressClass + if err := l.client.Get(ctx, client.ObjectKey{Name: name}, &obj); err != nil { + return nil, err + } + return &types.IngressClass{ + Object: &obj, + }, nil +} + +type ingressClassV1beta1Lister struct { + client client.Client +} + +func (l *ingressClassV1beta1Lister) List(ctx context.Context) ([]*types.IngressClass, error) { + var list networkingv1beta1.IngressClassList + if err := l.client.List(ctx, &list); err != nil { + return nil, err + } + + var result []*types.IngressClass + for _, item := range list.Items { + obj := item + result = append(result, &types.IngressClass{ + Object: &obj, + }) + } + return result, nil +} + +func (l *ingressClassV1beta1Lister) Get(ctx context.Context, name string) (*types.IngressClass, error) { + var obj networkingv1beta1.IngressClass + if err := l.client.Get(ctx, client.ObjectKey{Name: name}, &obj); err != nil { + return nil, err + } + return &types.IngressClass{ + Object: &obj, + }, nil +} + +func NewIngressClassLister(client client.Client, gvk schema.GroupVersionKind) IngressClassLister { + switch gvk { + case networkingv1.SchemeGroupVersion.WithKind("IngressClass"): + return &ingressClassV1Lister{client: client} + case networkingv1beta1.SchemeGroupVersion.WithKind("IngressClass"): + return &ingressClassV1beta1Lister{client: client} + } + return nil +} diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 0917cf815..b23565b3c 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -35,11 +35,13 @@ import ( corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" k8stypes "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -54,6 +56,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/provider" "github.com/apache/apisix-ingress-controller/internal/types" "github.com/apache/apisix-ingress-controller/internal/utils" + pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils" ) const ( @@ -93,10 +96,13 @@ func GetEnableReferenceGrant() bool { // IsDefaultIngressClass returns whether an IngressClass is the default IngressClass. func IsDefaultIngressClass(obj client.Object) bool { - if ingressClass, ok := obj.(*networkingv1.IngressClass); ok { - return ingressClass.Annotations[defaultIngressClassAnnotation] == "true" + switch obj.(type) { + case *networkingv1.IngressClass, *networkingv1beta1.IngressClass: + default: + return false } - return false + annotations := obj.GetAnnotations() + return annotations[defaultIngressClassAnnotation] == "true" } func acceptedMessage(kind string) string { @@ -1219,7 +1225,7 @@ func ListMatchingRequests( return requests } -func listIngressClassRequestsForGatewayProxy( +func listIngressClassV1beta1RequestsForGatewayProxy( ctx context.Context, c client.Client, obj client.Object, @@ -1231,7 +1237,7 @@ func listIngressClassRequestsForGatewayProxy( return nil } - ingressClassList := &networkingv1.IngressClassList{} + ingressClassList := &networkingv1beta1.IngressClassList{} if err := c.List(ctx, ingressClassList, client.MatchingFields{ indexer.IngressClassParametersRef: indexer.GenIndexKey(gatewayProxy.GetNamespace(), gatewayProxy.GetName()), }); err != nil { @@ -1253,44 +1259,54 @@ func listIngressClassRequestsForGatewayProxy( return requests } -func matchesIngressController(obj client.Object) bool { - ingressClass, ok := obj.(*networkingv1.IngressClass) +func listIngressClassRequestsForGatewayProxy( + ctx context.Context, + c client.Client, + obj client.Object, + logger logr.Logger, + listFunc func(context.Context, client.Object) []reconcile.Request, +) []reconcile.Request { + gatewayProxy, ok := obj.(*v1alpha1.GatewayProxy) if !ok { - return false + return nil } - return matchesController(ingressClass.Spec.Controller) -} -func matchesIngressClass(c client.Client, log logr.Logger, ingressClassName string) bool { - if ingressClassName == "" { - // Check for default ingress class - ingressClassList := &networkingv1.IngressClassList{} - if err := c.List(context.Background(), ingressClassList, client.MatchingFields{ - indexer.IngressClass: config.GetControllerName(), - }); err != nil { - log.Error(err, "failed to list ingress classes") - return false - } + ingressClassList := &networkingv1.IngressClassList{} + if err := c.List(ctx, ingressClassList, client.MatchingFields{ + indexer.IngressClassParametersRef: indexer.GenIndexKey(gatewayProxy.GetNamespace(), gatewayProxy.GetName()), + }); err != nil { + logger.Error(err, "failed to list ingress classes for gateway proxy", "gatewayproxy", gatewayProxy.GetName()) + return nil + } - // Find the ingress class that is marked as default - for _, ic := range ingressClassList.Items { - if IsDefaultIngressClass(&ic) && matchesController(ic.Spec.Controller) { - return true - } + requestSet := make(map[string]reconcile.Request) + for _, ingressClass := range ingressClassList.Items { + for _, req := range listFunc(ctx, &ingressClass) { + requestSet[req.String()] = req } - return false } - // Check if the specified ingress class is controlled by us - var ingressClass networkingv1.IngressClass - if err := c.Get(context.Background(), client.ObjectKey{Name: ingressClassName}, &ingressClass); err != nil { - log.Error(err, "failed to get ingress class", "ingressClass", ingressClassName) - return false + requests := make([]reconcile.Request, 0, len(requestSet)) + for _, req := range requestSet { + requests = append(requests, req) } + return requests +} +func matchesIngressController(obj client.Object) bool { + ingressClass := convertIngressClass(obj) return matchesController(ingressClass.Spec.Controller) } +func matchesIngressClass(ctx context.Context, c client.Client, log logr.Logger, ingressClassName string, apiVersion string) bool { + ic, err := GetIngressClass(ctx, c, log, ingressClassName, apiVersion) + if err != nil { + log.Error(err, "failed to get IngressClass", "ingressClassName", ingressClassName) + return false + } + return ic != nil +} + func ProcessIngressClassParameters(tctx *provider.TranslateContext, c client.Client, log logr.Logger, object client.Object, ingressClass *networkingv1.IngressClass) error { if ingressClass == nil || ingressClass.Spec.Parameters == nil { return nil @@ -1302,7 +1318,7 @@ func ProcessIngressClassParameters(tctx *provider.TranslateContext, c client.Cli parameters := ingressClass.Spec.Parameters // check if the parameters reference GatewayProxy if parameters.APIGroup != nil && *parameters.APIGroup == v1alpha1.GroupVersion.Group && parameters.Kind == KindGatewayProxy { - ns := object.GetNamespace() + ns := "default" if parameters.Namespace != nil { ns = *parameters.Namespace } @@ -1368,7 +1384,40 @@ func ProcessIngressClassParameters(tctx *provider.TranslateContext, c client.Cli return nil } -func GetIngressClass(ctx context.Context, c client.Client, log logr.Logger, ingressClassName string) (*networkingv1.IngressClass, error) { +func GetIngressClassV1Beta1(ctx context.Context, c client.Client, log logr.Logger, ingressClassName string) (*networkingv1beta1.IngressClass, error) { + if ingressClassName == "" { + // Check for default ingress class + ingressClassList := &networkingv1beta1.IngressClassList{} + if err := c.List(ctx, ingressClassList, client.MatchingFields{ + indexer.IngressClass: config.GetControllerName(), + }); err != nil { + log.Error(err, "failed to list ingress classes") + return nil, err + } + + // Find the ingress class that is marked as default + for _, ic := range ingressClassList.Items { + if IsDefaultIngressClass(&ic) && matchesController(ic.Spec.Controller) { + return &ic, nil + } + } + return nil, errors.New("no default ingress class found") + } + + // Check if the specified ingress class is controlled by us + var ingressClass networkingv1beta1.IngressClass + if err := c.Get(ctx, client.ObjectKey{Name: ingressClassName}, &ingressClass); err != nil { + return nil, err + } + + if matchesController(ingressClass.Spec.Controller) { + return &ingressClass, nil + } + + return nil, errors.New("ingress class is not controlled by us") +} + +func GetIngressClassv1(ctx context.Context, c client.Client, log logr.Logger, ingressClassName string) (*networkingv1.IngressClass, error) { if ingressClassName == "" { // Check for default ingress class ingressClassList := &networkingv1.IngressClassList{} @@ -1385,6 +1434,7 @@ func GetIngressClass(ctx context.Context, c client.Client, log logr.Logger, ingr return &ic, nil } } + log.V(1).Info("no default ingress class found") return nil, errors.New("no default ingress class found") } @@ -1401,6 +1451,21 @@ func GetIngressClass(ctx context.Context, c client.Client, log logr.Logger, ingr return nil, errors.New("ingress class is not controlled by us") } +func GetIngressClass(ctx context.Context, c client.Client, log logr.Logger, ingressClassName string, apiVersion string) (*networkingv1.IngressClass, error) { + switch apiVersion { + case networkingv1beta1.SchemeGroupVersion.String(), "": + icBeta, err := GetIngressClassV1Beta1(ctx, c, log, ingressClassName) + if err != nil { + return nil, err + } + return convertIngressClass(icBeta), nil + case networkingv1.SchemeGroupVersion.String(): + return GetIngressClassv1(ctx, c, log, ingressClassName) + default: + return nil, fmt.Errorf("unsupported IngressClass API version: %s", apiVersion) + } +} + // distinctRequests distinct the requests func distinctRequests(requests []reconcile.Request) []reconcile.Request { uniqueRequests := make(map[string]reconcile.Request) @@ -1416,6 +1481,10 @@ func distinctRequests(requests []reconcile.Request) []reconcile.Request { } func addProviderEndpointsToTranslateContext(tctx *provider.TranslateContext, c client.Client, serviceNN k8stypes.NamespacedName) error { + return addProviderEndpointsToTranslateContextWithEndpointSliceSupport(tctx, c, serviceNN, true) +} + +func addProviderEndpointsToTranslateContextWithEndpointSliceSupport(tctx *provider.TranslateContext, c client.Client, serviceNN k8stypes.NamespacedName, supportsEndpointSlice bool) error { log.Debugw("to process provider endpoints by provider.service", zap.Any("service", serviceNN)) var ( service corev1.Service @@ -1426,19 +1495,37 @@ func addProviderEndpointsToTranslateContext(tctx *provider.TranslateContext, c c } tctx.Services[serviceNN] = &service - // get es - var ( - esList discoveryv1.EndpointSliceList - ) - if err := c.List(tctx, &esList, - client.InNamespace(serviceNN.Namespace), - client.MatchingLabels{ - discoveryv1.LabelServiceName: serviceNN.Name, - }); err != nil { - log.Errorw("failed to get endpoints for GatewayProxy provider", zap.Error(err), zap.Any("endpoints", serviceNN)) - return err + // Conditionally get EndpointSlice or Endpoints based on cluster API support + if supportsEndpointSlice { + // get es + var ( + esList discoveryv1.EndpointSliceList + ) + if err := c.List(tctx, &esList, + client.InNamespace(serviceNN.Namespace), + client.MatchingLabels{ + discoveryv1.LabelServiceName: serviceNN.Name, + }); err != nil { + log.Errorw("failed to get endpoints for GatewayProxy provider", zap.Error(err), zap.Any("endpoints", serviceNN)) + return err + } + tctx.EndpointSlices[serviceNN] = esList.Items + } else { + // Fallback to Endpoints API for Kubernetes 1.18 compatibility + var endpoints corev1.Endpoints + if err := c.Get(tctx, serviceNN, &endpoints); err != nil { + if client.IgnoreNotFound(err) != nil { + log.Errorw("failed to get endpoints for GatewayProxy provider", zap.Error(err), zap.Any("endpoints", serviceNN)) + return err + } + // If endpoints not found, create empty EndpointSlice list + tctx.EndpointSlices[serviceNN] = []discoveryv1.EndpointSlice{} + } else { + // Convert Endpoints to EndpointSlice format for internal consistency + convertedEndpointSlices := pkgutils.ConvertEndpointsToEndpointSlice(&endpoints) + tctx.EndpointSlices[serviceNN] = convertedEndpointSlices + } } - tctx.EndpointSlices[serviceNN] = esList.Items return nil } @@ -1483,3 +1570,37 @@ func MatchConsumerGatewayRef(ctx context.Context, c client.Client, log logr.Logg } return matchesController(string(gatewayClass.Spec.ControllerName)) } + +func convertIngressClass(obj client.Object) *networkingv1.IngressClass { + switch t := obj.(type) { + case *networkingv1beta1.IngressClass: + icv1 := &networkingv1.IngressClass{ + TypeMeta: t.TypeMeta, + ObjectMeta: t.ObjectMeta, + Spec: networkingv1.IngressClassSpec{ + Controller: t.Spec.Controller, + Parameters: &networkingv1.IngressClassParametersReference{ + APIGroup: t.Spec.Parameters.APIGroup, + Kind: t.Spec.Parameters.Kind, + Name: t.Spec.Parameters.Name, + }, + }, + } + icv1.APIVersion = networkingv1.SchemeGroupVersion.String() + return icv1 + case *networkingv1.IngressClass: + return t + default: + panic(fmt.Sprintf("unexpected type %T for IngressClass", t)) + } +} + +func GetIngressClassName(obj client.Object) string { + switch t := obj.(type) { + case *networkingv1.Ingress: + return ptr.Deref(t.Spec.IngressClassName, "") + case *networkingv1beta1.IngressClass: + return "" + } + return "" +} diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go index e1f19df8f..a7158dfb1 100644 --- a/internal/manager/controllers.go +++ b/internal/manager/controllers.go @@ -21,6 +21,8 @@ import ( "context" netv1 "k8s.io/api/networking/v1" + v1 "k8s.io/api/networking/v1" + "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -48,6 +50,7 @@ import ( // +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=endpoints,verbs=get;list;watch // CustomResourceDefinition v2 // +kubebuilder:rbac:groups=apisix.apache.org,resources=apisixconsumers,verbs=get;list;watch @@ -102,6 +105,18 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro setupLog := ctrl.LoggerFrom(ctx).WithName("setup") var controllers []Controller + icgv := v1.SchemeGroupVersion + if !utils.HasAPIResource(mgr, &v1.IngressClass{}) { + setupLog.Info("IngressClass v1 not found, falling back to IngressClass v1beta1") + icgv = v1beta1.SchemeGroupVersion + controllers = append(controllers, &controller.IngressClassV1beta1Reconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("IngressClass"), + Provider: pro, + }) + } + // Gateway API Controllers - conditional registration based on API availability for resource, controller := range map[client.Object]Controller{ &gatewayv1.GatewayClass{}: &controller.GatewayClassReconciler{ @@ -133,17 +148,7 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Updater: updater, Readier: readier, }, - } { - if utils.HasAPIResource(mgr, resource) { - controllers = append(controllers, controller) - } else { - setupLog.Info("Skipping controller setup, API not found in cluster", "api", utils.FormatGVK(resource)) - } - } - - controllers = append(controllers, []Controller{ - // Core Kubernetes Controllers - always register these - &controller.IngressReconciler{ + &v1.Ingress{}: &controller.IngressReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Ingress"), @@ -151,35 +156,46 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Updater: updater, Readier: readier, }, - &controller.IngressClassReconciler{ + &v1.IngressClass{}: &controller.IngressClassReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("IngressClass"), Provider: pro, }, + } { + if utils.HasAPIResource(mgr, resource) { + controllers = append(controllers, controller) + } else { + setupLog.Info("Skipping controller setup, API not found in cluster", "api", utils.FormatGVK(resource)) + } + } + + controllers = append(controllers, []Controller{ // Gateway Proxy Controller - always register this as it is core to the controller &controller.GatewayProxyController{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("GatewayProxy"), Provider: pro, + ICGV: icgv, }, - // APISIX v2 Controllers - always register these as they are core to the controller - &controller.ApisixGlobalRuleReconciler{ + &controller.ApisixRouteReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixGlobalRule"), + Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixRoute"), Provider: pro, Updater: updater, Readier: readier, + ICGV: icgv, }, - &controller.ApisixRouteReconciler{ + &controller.ApisixGlobalRuleReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixRoute"), + Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixGlobalRule"), Provider: pro, Updater: updater, Readier: readier, + ICGV: icgv, }, &controller.ApisixConsumerReconciler{ Client: mgr.GetClient(), @@ -188,12 +204,14 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Provider: pro, Updater: updater, Readier: readier, + ICGV: icgv, }, &controller.ApisixPluginConfigReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixPluginConfig"), Updater: updater, + ICGV: icgv, }, &controller.ApisixTlsReconciler{ Client: mgr.GetClient(), @@ -202,12 +220,14 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Provider: pro, Updater: updater, Readier: readier, + ICGV: icgv, }, &controller.ApisixUpstreamReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixUpstream"), Updater: updater, + ICGV: icgv, }, }...) @@ -215,8 +235,20 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro return controllers, nil } -func registerReadinessGVK(c client.Client, readier readiness.ReadinessManager) { +var skip = true + +func registerReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager) { + if skip { + return + } + c := mgr.GetClient() log := ctrl.LoggerFrom(context.Background()).WithName("readiness") + + icgvk := types.GvkOf(&v1.IngressClass{}) + if !utils.HasAPIResource(mgr, &v1.IngressClass{}) { + icgvk = types.GvkOf(&v1beta1.IngressClass{}) + } + readier.RegisterGVK([]readiness.GVKConfig{ { GVKs: []schema.GroupVersionKind{ @@ -234,7 +266,7 @@ func registerReadinessGVK(c client.Client, readier readiness.ReadinessManager) { }, Filter: readiness.GVKFilter(func(obj *unstructured.Unstructured) bool { icName, _, _ := unstructured.NestedString(obj.Object, "spec", "ingressClassName") - ingressClass, _ := controller.GetIngressClass(context.Background(), c, log, icName) + ingressClass, _ := controller.GetIngressClass(context.Background(), c, log, icName, icgvk.Version) return ingressClass != nil }), }, diff --git a/internal/manager/run.go b/internal/manager/run.go index b75b383d8..52b12ceb0 100644 --- a/internal/manager/run.go +++ b/internal/manager/run.go @@ -23,6 +23,7 @@ import ( "os" "github.com/go-logr/logr" + networkingv1beta1 "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -65,6 +66,9 @@ func init() { if err := v1beta1.Install(scheme); err != nil { panic(err) } + if err := networkingv1beta1.AddToScheme(scheme); err != nil { + panic(err) + } // +kubebuilder:scaffold:scheme } @@ -152,7 +156,7 @@ func Run(ctx context.Context, logger logr.Logger) error { } readier := readiness.NewReadinessManager(mgr.GetClient()) - registerReadinessGVK(mgr.GetClient(), readier) + registerReadinessGVK(mgr, readier) if err := mgr.Add(readier); err != nil { setupLog.Error(err, "unable to add readiness manager") diff --git a/internal/types/ingressclass.go b/internal/types/ingressclass.go new file mode 100644 index 000000000..34c4456ea --- /dev/null +++ b/internal/types/ingressclass.go @@ -0,0 +1,9 @@ +package types + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type IngressClass struct { + client.Object +} diff --git a/internal/types/k8s.go b/internal/types/k8s.go index d83158fe0..5b20b47cc 100644 --- a/internal/types/k8s.go +++ b/internal/types/k8s.go @@ -20,7 +20,10 @@ package types import ( corev1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" + netv1beta1 "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/runtime/schema" + kschema "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" "github.com/apache/apisix-ingress-controller/api/v1alpha1" @@ -60,7 +63,7 @@ func KindOf(obj any) string { return KindGatewayClass case *netv1.Ingress: return KindIngress - case *netv1.IngressClass: + case *netv1.IngressClass, *netv1beta1.IngressClass: return KindIngressClass case *corev1.Secret: return KindSecret @@ -93,6 +96,26 @@ func KindOf(obj any) string { } } +func TypeObject(gvk schema.GroupVersionKind) client.Object { + obj, err := kschema.Scheme.New(gvk) + if err != nil { + panic(err) + } + return obj.(client.Object) +} + +func TypeList(gvk schema.GroupVersionKind) client.ObjectList { + obj, err := kschema.Scheme.New(gvk) + if err != nil { + panic(err) + } + list, ok := obj.(client.ObjectList) + if !ok { + panic("object is not a list") + } + return list +} + func GvkOf(obj any) schema.GroupVersionKind { kind := KindOf(obj) switch obj.(type) { @@ -100,6 +123,8 @@ func GvkOf(obj any) schema.GroupVersionKind { return gatewayv1.SchemeGroupVersion.WithKind(kind) case *netv1.Ingress, *netv1.IngressClass: return netv1.SchemeGroupVersion.WithKind(kind) + case *netv1beta1.IngressClass: + return netv1beta1.SchemeGroupVersion.WithKind(kind) case *corev1.Secret, *corev1.Service: return corev1.SchemeGroupVersion.WithKind(kind) case *v2.ApisixRoute: diff --git a/pkg/utils/cluster.go b/pkg/utils/cluster.go index 77d7ec48c..488cd451f 100644 --- a/pkg/utils/cluster.go +++ b/pkg/utils/cluster.go @@ -22,6 +22,7 @@ import ( "k8s.io/client-go/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "github.com/apache/apisix-ingress-controller/internal/types" ) @@ -36,7 +37,12 @@ func HasAPIResource(mgr ctrl.Manager, obj client.Object) bool { // HasAPIResourceWithLogger is the same as HasAPIResource but accepts a custom logger // for more detailed debugging information. func HasAPIResourceWithLogger(mgr ctrl.Manager, obj client.Object, logger logr.Logger) bool { - gvk := types.GvkOf(obj) + gvk, err := apiutil.GVKForObject(obj, mgr.GetScheme()) + if err != nil { + logger.Info("cannot derive GVK from scheme", "error", err) + return false + } + groupVersion := gvk.GroupVersion().String() logger = logger.WithValues( @@ -63,6 +69,7 @@ func HasAPIResourceWithLogger(mgr ctrl.Manager, obj client.Object, logger logr.L // Check if the specific kind exists in the resource list for _, res := range apiResources.APIResources { if res.Kind == gvk.Kind { + logger.Info("API resource kind found in group/version", "kind", res.Kind) return true } } @@ -73,5 +80,5 @@ func HasAPIResourceWithLogger(mgr ctrl.Manager, obj client.Object, logger logr.L func FormatGVK(obj client.Object) string { gvk := types.GvkOf(obj) - return gvk.GroupVersion().String() + "." + gvk.Kind + return gvk.String() } diff --git a/pkg/utils/endpoints.go b/pkg/utils/endpoints.go new file mode 100644 index 000000000..655260bd2 --- /dev/null +++ b/pkg/utils/endpoints.go @@ -0,0 +1,150 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package utils + +import ( + "fmt" + "net" + + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +// ConvertEndpointsToEndpointSlice converts a Kubernetes Endpoints object to one +// or more EndpointSlice objects, supporting IPv4/IPv6 dual stack. +// This function is used to provide backward compatibility for Kubernetes 1.18 clusters that don't +// have EndpointSlice support but still use the older Endpoints API. +// +// The conversion follows these rules: +// - Each Endpoints subset is split into separate IPv4 and IPv6 EndpointSlices +// - Uses net.ParseIP for reliable address family detection instead of string matching +// - IPv4 and IPv6 endpoints from the same subset are separated into different slices +// - Naming convention: --v4 / -v6 +// - Port information is preserved +// - Ready state is mapped from Endpoints addresses vs notReadyAddresses +// +// Note: Some EndpointSlice features like topology and conditions may not be fully represented +// since they don't exist in the Endpoints API. +func ConvertEndpointsToEndpointSlice(ep *corev1.Endpoints) []discoveryv1.EndpointSlice { + if ep == nil { + return nil + } + + var endpointSlices []discoveryv1.EndpointSlice + + // If there are no subsets, create an empty EndpointSlice + if len(ep.Subsets) == 0 { + endpointSlices = append(endpointSlices, discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: ep.Name + "-v4", // Default to v4 + Namespace: ep.Namespace, + Labels: map[string]string{discoveryv1.LabelServiceName: ep.Name}, + OwnerReferences: ep.OwnerReferences, + }, + AddressType: discoveryv1.AddressTypeIPv4, + Ports: []discoveryv1.EndpointPort{}, + Endpoints: []discoveryv1.Endpoint{}, + }) + return endpointSlices + } + + for i, subset := range ep.Subsets { + // Create ports array + var ports []discoveryv1.EndpointPort + for _, p := range subset.Ports { + epPort := discoveryv1.EndpointPort{ + Port: &p.Port, + Protocol: &p.Protocol, + } + if p.Name != "" { + epPort.Name = &p.Name + } + ports = append(ports, epPort) + } + + // Separate IPv4 and IPv6 addresses + var ( + ipv4Endpoints []discoveryv1.Endpoint + ipv6Endpoints []discoveryv1.Endpoint + ) + buildEndpoint := func(addr corev1.EndpointAddress, ready bool) discoveryv1.Endpoint { + e := discoveryv1.Endpoint{ + Addresses: []string{addr.IP}, + Conditions: discoveryv1.EndpointConditions{ + Ready: ptr.To(ready), + }, + } + if addr.TargetRef != nil { + e.TargetRef = addr.TargetRef + } + if addr.Hostname != "" { + e.Hostname = &addr.Hostname + } + return e + } + + // Process ready addresses + for _, a := range subset.Addresses { + if isIPv6(a.IP) { + ipv6Endpoints = append(ipv6Endpoints, buildEndpoint(a, true)) + } else { + ipv4Endpoints = append(ipv4Endpoints, buildEndpoint(a, true)) + } + } + // Process not ready addresses + for _, a := range subset.NotReadyAddresses { + if isIPv6(a.IP) { + ipv6Endpoints = append(ipv6Endpoints, buildEndpoint(a, false)) + } else { + ipv4Endpoints = append(ipv4Endpoints, buildEndpoint(a, false)) + } + } + + // Create EndpointSlices for each address type + makeSlice := func(suffix string, addrType discoveryv1.AddressType, eps []discoveryv1.Endpoint) discoveryv1.EndpointSlice { + return discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%d-%s", ep.Name, i, suffix), + Namespace: ep.Namespace, + Labels: map[string]string{discoveryv1.LabelServiceName: ep.Name}, + OwnerReferences: ep.OwnerReferences, + }, + AddressType: addrType, + Ports: ports, + Endpoints: eps, + } + } + + if len(ipv4Endpoints) > 0 { + endpointSlices = append(endpointSlices, makeSlice("v4", discoveryv1.AddressTypeIPv4, ipv4Endpoints)) + } + if len(ipv6Endpoints) > 0 { + endpointSlices = append(endpointSlices, makeSlice("v6", discoveryv1.AddressTypeIPv6, ipv6Endpoints)) + } + } + + return endpointSlices +} + +// isIPv6 uses net.ParseIP to determine if an IP address is IPv6 +func isIPv6(ip string) bool { + parsed := net.ParseIP(ip) + return parsed != nil && parsed.To4() == nil +} diff --git a/pkg/utils/endpoints_test.go b/pkg/utils/endpoints_test.go new file mode 100644 index 000000000..a032f08dd --- /dev/null +++ b/pkg/utils/endpoints_test.go @@ -0,0 +1,503 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package utils + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +func TestIsIPv6(t *testing.T) { + tests := []struct { + name string + ip string + want bool + }{ + { + name: "IPv4 address", + ip: "192.168.1.1", + want: false, + }, + { + name: "IPv6 address", + ip: "2001:db8::1", + want: true, + }, + { + name: "IPv6 loopback", + ip: "::1", + want: true, + }, + { + name: "IPv4 loopback", + ip: "127.0.0.1", + want: false, + }, + { + name: "invalid IP", + ip: "invalid", + want: false, + }, + { + name: "IPv4-mapped IPv6", + ip: "::ffff:192.168.1.1", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isIPv6(tt.ip); got != tt.want { + t.Errorf("isIPv6() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestConvertEndpointsToEndpointSlice(t *testing.T) { + tests := []struct { + name string + endpoints *corev1.Endpoints + want []discoveryv1.EndpointSlice + }{ + { + name: "nil endpoints", + endpoints: nil, + want: nil, + }, + { + name: "empty subsets", + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{}, + }, + want: []discoveryv1.EndpointSlice{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service-v4", + Namespace: "default", + Labels: map[string]string{ + discoveryv1.LabelServiceName: "test-service", + }, + }, + AddressType: discoveryv1.AddressTypeIPv4, + Ports: []discoveryv1.EndpointPort{}, + Endpoints: []discoveryv1.Endpoint{}, + }, + }, + }, + { + name: "IPv4 only", + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + {IP: "192.168.1.1"}, + {IP: "192.168.1.2"}, + }, + Ports: []corev1.EndpointPort{ + {Port: 80, Protocol: corev1.ProtocolTCP, Name: "http"}, + }, + }, + }, + }, + want: []discoveryv1.EndpointSlice{ + createTestEndpointSlice("test-service-0-v4", discoveryv1.AddressTypeIPv4, + []discoveryv1.EndpointPort{createHTTPPort()}, + []discoveryv1.Endpoint{ + createReadyEndpoint("192.168.1.1"), + createReadyEndpoint("192.168.1.2"), + }), + }, + }, + { + name: "IPv6 only", + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + {IP: "2001:db8::1"}, + {IP: "2001:db8::2"}, + }, + Ports: []corev1.EndpointPort{ + {Port: 80, Protocol: corev1.ProtocolTCP, Name: "http"}, + }, + }, + }, + }, + want: []discoveryv1.EndpointSlice{ + createTestEndpointSlice("test-service-0-v6", discoveryv1.AddressTypeIPv6, + []discoveryv1.EndpointPort{createHTTPPort()}, + []discoveryv1.Endpoint{ + createReadyEndpoint("2001:db8::1"), + createReadyEndpoint("2001:db8::2"), + }), + }, + }, + { + name: "dual stack (IPv4 and IPv6)", + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + {IP: "192.168.1.1"}, + {IP: "2001:db8::1"}, + }, + Ports: []corev1.EndpointPort{ + {Port: 80, Protocol: corev1.ProtocolTCP, Name: "http"}, + }, + }, + }, + }, + want: []discoveryv1.EndpointSlice{ + createTestEndpointSlice("test-service-0-v4", discoveryv1.AddressTypeIPv4, + []discoveryv1.EndpointPort{createHTTPPort()}, + []discoveryv1.Endpoint{createReadyEndpoint("192.168.1.1")}), + createTestEndpointSlice("test-service-0-v6", discoveryv1.AddressTypeIPv6, + []discoveryv1.EndpointPort{createHTTPPort()}, + []discoveryv1.Endpoint{createReadyEndpoint("2001:db8::1")}), + }, + }, + { + name: "ready and not ready addresses", + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + { + IP: "192.168.1.1", + }, + }, + NotReadyAddresses: []corev1.EndpointAddress{ + { + IP: "192.168.1.2", + }, + }, + Ports: []corev1.EndpointPort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + Name: "http", + }, + }, + }, + }, + }, + want: []discoveryv1.EndpointSlice{ + createTestEndpointSlice("test-service-0-v4", discoveryv1.AddressTypeIPv4, + []discoveryv1.EndpointPort{createHTTPPort()}, + []discoveryv1.Endpoint{ + createReadyEndpoint("192.168.1.1"), + createNotReadyEndpoint("192.168.1.2"), + }), + }, + }, + { + name: "multiple subsets", + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + { + IP: "192.168.1.1", + }, + }, + Ports: []corev1.EndpointPort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + Name: "http", + }, + }, + }, + { + Addresses: []corev1.EndpointAddress{ + { + IP: "2001:db8::1", + }, + }, + Ports: []corev1.EndpointPort{ + { + Port: 443, + Protocol: corev1.ProtocolTCP, + Name: "https", + }, + }, + }, + }, + }, + want: []discoveryv1.EndpointSlice{ + createTestEndpointSlice("test-service-0-v4", discoveryv1.AddressTypeIPv4, + []discoveryv1.EndpointPort{createHTTPPort()}, + []discoveryv1.Endpoint{createReadyEndpoint("192.168.1.1")}), + createTestEndpointSlice("test-service-1-v6", discoveryv1.AddressTypeIPv6, + []discoveryv1.EndpointPort{createHTTPSPort(443)}, + []discoveryv1.Endpoint{createReadyEndpoint("2001:db8::1")}), + }, + }, + { + name: "with target ref and hostname", + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + { + IP: "192.168.1.1", + Hostname: "pod-1", + TargetRef: &corev1.ObjectReference{ + Kind: "Pod", + Name: "pod-1", + Namespace: "default", + }, + }, + }, + Ports: []corev1.EndpointPort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + }, + }, + }, + }, + }, + want: []discoveryv1.EndpointSlice{ + createTestEndpointSlice("test-service-0-v4", discoveryv1.AddressTypeIPv4, + []discoveryv1.EndpointPort{createPlainPort(80)}, + []discoveryv1.Endpoint{createEndpointWithHostname("192.168.1.1", "pod-1")}), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertEndpointsToEndpointSlice(tt.endpoints) + if len(got) != len(tt.want) { + t.Errorf("ConvertEndpointsToEndpointSlice() returned %d slices, want %d", len(got), len(tt.want)) + return + } + + for i, slice := range got { + if i >= len(tt.want) { + t.Errorf("ConvertEndpointsToEndpointSlice() returned more slices than expected") + return + } + assertEndpointSliceEqual(t, slice, tt.want[i], i) + } + }) + } +} + +func assertEndpointSliceEqual(t *testing.T, got, want discoveryv1.EndpointSlice, index int) { + t.Helper() + + if got.Name != want.Name { + t.Errorf("EndpointSlice[%d].Name = %v, want %v", index, got.Name, want.Name) + } + if got.Namespace != want.Namespace { + t.Errorf("EndpointSlice[%d].Namespace = %v, want %v", index, got.Namespace, want.Namespace) + } + if len(got.Labels) != len(want.Labels) { + t.Errorf("EndpointSlice[%d].Labels length = %v, want %v", index, len(got.Labels), len(want.Labels)) + } + for k, v := range want.Labels { + if got.Labels[k] != v { + t.Errorf("EndpointSlice[%d].Labels[%s] = %v, want %v", index, k, got.Labels[k], v) + } + } + + if got.AddressType != want.AddressType { + t.Errorf("EndpointSlice[%d].AddressType = %v, want %v", index, got.AddressType, want.AddressType) + } + + assertEndpointPortsEqual(t, got.Ports, want.Ports, index) + assertEndpointsEqual(t, got.Endpoints, want.Endpoints, index) +} + +func assertEndpointPortsEqual(t *testing.T, got, want []discoveryv1.EndpointPort, sliceIndex int) { + t.Helper() + + if len(got) != len(want) { + t.Errorf("EndpointSlice[%d].Ports length = %v, want %v", sliceIndex, len(got), len(want)) + return + } + + for j, port := range got { + if j >= len(want) { + continue + } + wantPort := want[j] + if (port.Name == nil) != (wantPort.Name == nil) || (port.Name != nil && *port.Name != *wantPort.Name) { + t.Errorf("EndpointSlice[%d].Ports[%d].Name = %v, want %v", sliceIndex, j, port.Name, wantPort.Name) + } + if *port.Port != *wantPort.Port { + t.Errorf("EndpointSlice[%d].Ports[%d].Port = %v, want %v", sliceIndex, j, *port.Port, *wantPort.Port) + } + if *port.Protocol != *wantPort.Protocol { + t.Errorf("EndpointSlice[%d].Ports[%d].Protocol = %v, want %v", sliceIndex, j, *port.Protocol, *wantPort.Protocol) + } + } +} + +func assertEndpointsEqual(t *testing.T, got, want []discoveryv1.Endpoint, sliceIndex int) { + t.Helper() + + if len(got) != len(want) { + t.Errorf("EndpointSlice[%d].Endpoints length = %v, want %v", sliceIndex, len(got), len(want)) + return + } + + for j, endpoint := range got { + if j >= len(want) { + continue + } + wantEndpoint := want[j] + + if len(endpoint.Addresses) != len(wantEndpoint.Addresses) { + t.Errorf("EndpointSlice[%d].Endpoints[%d].Addresses length = %v, want %v", sliceIndex, j, len(endpoint.Addresses), len(wantEndpoint.Addresses)) + } + for k, addr := range endpoint.Addresses { + if k >= len(wantEndpoint.Addresses) { + continue + } + if addr != wantEndpoint.Addresses[k] { + t.Errorf("EndpointSlice[%d].Endpoints[%d].Addresses[%d] = %v, want %v", sliceIndex, j, k, addr, wantEndpoint.Addresses[k]) + } + } + + if *endpoint.Conditions.Ready != *wantEndpoint.Conditions.Ready { + t.Errorf("EndpointSlice[%d].Endpoints[%d].Conditions.Ready = %v, want %v", sliceIndex, j, *endpoint.Conditions.Ready, *wantEndpoint.Conditions.Ready) + } + + if (endpoint.Hostname == nil) != (wantEndpoint.Hostname == nil) || (endpoint.Hostname != nil && *endpoint.Hostname != *wantEndpoint.Hostname) { + t.Errorf("EndpointSlice[%d].Endpoints[%d].Hostname = %v, want %v", sliceIndex, j, endpoint.Hostname, wantEndpoint.Hostname) + } + + if (endpoint.TargetRef == nil) != (wantEndpoint.TargetRef == nil) { + t.Errorf("EndpointSlice[%d].Endpoints[%d].TargetRef presence mismatch", sliceIndex, j) + } else if endpoint.TargetRef != nil && wantEndpoint.TargetRef != nil { + if endpoint.TargetRef.Kind != wantEndpoint.TargetRef.Kind || + endpoint.TargetRef.Name != wantEndpoint.TargetRef.Name || + endpoint.TargetRef.Namespace != wantEndpoint.TargetRef.Namespace { + t.Errorf("EndpointSlice[%d].Endpoints[%d].TargetRef = %v, want %v", sliceIndex, j, endpoint.TargetRef, wantEndpoint.TargetRef) + } + } + } +} + +func createTestEndpointSlice(name string, addressType discoveryv1.AddressType, + ports []discoveryv1.EndpointPort, endpoints []discoveryv1.Endpoint) discoveryv1.EndpointSlice { + return discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + Labels: map[string]string{ + discoveryv1.LabelServiceName: "test-service", + }, + }, + AddressType: addressType, + Ports: ports, + Endpoints: endpoints, + } +} + +func createHTTPPort() discoveryv1.EndpointPort { + return discoveryv1.EndpointPort{ + Name: ptr.To("http"), + Port: ptr.To[int32](80), + Protocol: ptr.To(corev1.ProtocolTCP), + } +} + +func createHTTPSPort(port int32) discoveryv1.EndpointPort { + return discoveryv1.EndpointPort{ + Name: ptr.To("https"), + Port: ptr.To(port), + Protocol: ptr.To(corev1.ProtocolTCP), + } +} + +func createPlainPort(port int32) discoveryv1.EndpointPort { + return discoveryv1.EndpointPort{ + Port: ptr.To(port), + Protocol: ptr.To(corev1.ProtocolTCP), + } +} + +func createReadyEndpoint(address string) discoveryv1.Endpoint { + return discoveryv1.Endpoint{ + Addresses: []string{address}, + Conditions: discoveryv1.EndpointConditions{ + Ready: ptr.To(true), + }, + } +} + +func createNotReadyEndpoint(address string) discoveryv1.Endpoint { + return discoveryv1.Endpoint{ + Addresses: []string{address}, + Conditions: discoveryv1.EndpointConditions{ + Ready: ptr.To(false), + }, + } +} + +func createEndpointWithHostname(address, hostname string) discoveryv1.Endpoint { + return discoveryv1.Endpoint{ + Addresses: []string{address}, + Conditions: discoveryv1.EndpointConditions{ + Ready: ptr.To(true), + }, + Hostname: ptr.To(hostname), + TargetRef: &corev1.ObjectReference{ + Kind: "Pod", + Name: hostname, + Namespace: "default", + }, + } +} diff --git a/test/e2e/crds/v2/basic.go b/test/e2e/crds/v2/basic.go index 1d16a9f78..5538b4134 100644 --- a/test/e2e/crds/v2/basic.go +++ b/test/e2e/crds/v2/basic.go @@ -24,6 +24,37 @@ import ( "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" ) +const gatewayProxyYaml = ` +apiVersion: apisix.apache.org/v1alpha1 +kind: GatewayProxy +metadata: + name: apisix-proxy-config + namespace: default +spec: + provider: + type: ControlPlane + controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" +` + +const ingressClassYaml = ` +apiVersion: networking.k8s.io/%s +kind: IngressClass +metadata: + name: apisix +spec: + controller: "apisix.apache.org/apisix-ingress-controller" + parameters: + apiGroup: "apisix.apache.org" + kind: "GatewayProxy" + name: "apisix-proxy-config" +` + var _ = Describe("APISIX Standalone Basic Tests", Label("apisix.apache.org", "v2", "basic"), func() { s := scaffold.NewScaffold(&scaffold.Options{ ControllerName: "apisix.apache.org/apisix-ingress-controller", diff --git a/test/e2e/crds/v2/consumer.go b/test/e2e/crds/v2/consumer.go index ee243fb1e..20f5a228e 100644 --- a/test/e2e/crds/v2/consumer.go +++ b/test/e2e/crds/v2/consumer.go @@ -49,7 +49,8 @@ var _ = Describe("Test ApisixConsumer", Label("apisix.apache.org", "v2", "apisix time.Sleep(5 * time.Second) By("create IngressClass") - err = s.CreateResourceFromStringWithNamespace(ingressClassYaml, "") + ingressClass := fmt.Sprintf(ingressClassYaml, framework.IngressVersion) + err = s.CreateResourceFromStringWithNamespace(ingressClass, "") Expect(err).NotTo(HaveOccurred(), "creating IngressClass") time.Sleep(5 * time.Second) }) diff --git a/test/e2e/crds/v2/globalrule.go b/test/e2e/crds/v2/globalrule.go index 738d43e0d..65bc47801 100644 --- a/test/e2e/crds/v2/globalrule.go +++ b/test/e2e/crds/v2/globalrule.go @@ -25,65 +25,32 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/apache/apisix-ingress-controller/test/e2e/framework" "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" ) -const gatewayProxyYaml = ` -apiVersion: apisix.apache.org/v1alpha1 -kind: GatewayProxy -metadata: - name: apisix-proxy-config - namespace: default -spec: - provider: - type: ControlPlane - controlPlane: - endpoints: - - %s - auth: - type: AdminKey - adminKey: - value: "%s" -` - -const ingressClassYaml = ` -apiVersion: networking.k8s.io/v1 -kind: IngressClass -metadata: - name: apisix -spec: - controller: "apisix.apache.org/apisix-ingress-controller" - parameters: - apiGroup: "apisix.apache.org" - kind: "GatewayProxy" - name: "apisix-proxy-config" - namespace: "default" - scope: "Namespace" -` - var _ = Describe("Test GlobalRule", Label("apisix.apache.org", "v2", "apisixglobalrule"), func() { s := scaffold.NewScaffold(&scaffold.Options{ ControllerName: "apisix.apache.org/apisix-ingress-controller", }) - var ingressYaml = ` -apiVersion: networking.k8s.io/v1 -kind: Ingress + var defaultRoute = ` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute metadata: - name: test-ingress + name: default spec: ingressClassName: apisix - rules: - - host: globalrule.example.com - http: + http: + - name: rule0 + match: + hosts: + - globalrule.example.com paths: - - path: / - pathType: Prefix - backend: - service: - name: httpbin-service-e2e-test - port: - number: 80 + - /* + backends: + - serviceName: httpbin-service-e2e-test + servicePort: 80 ` Context("ApisixGlobalRule Basic Operations", func() { @@ -95,13 +62,14 @@ spec: time.Sleep(5 * time.Second) By("create IngressClass") - err = s.CreateResourceFromStringWithNamespace(ingressClassYaml, "") + ingressClass := fmt.Sprintf(ingressClassYaml, framework.IngressVersion) + err = s.CreateResourceFromStringWithNamespace(ingressClass, "") Expect(err).NotTo(HaveOccurred(), "creating IngressClass") time.Sleep(5 * time.Second) - By("create Ingress") - err = s.CreateResourceFromString(ingressYaml) - Expect(err).NotTo(HaveOccurred(), "creating Ingress") + By("create ApisixRoute") + err = s.CreateResourceFromString(defaultRoute) + Expect(err).NotTo(HaveOccurred(), "creating ApisixRoute") time.Sleep(5 * time.Second) By("verify Ingress works") diff --git a/test/e2e/crds/v2/pluginconfig.go b/test/e2e/crds/v2/pluginconfig.go index 589427eb9..ff50d5ccd 100644 --- a/test/e2e/crds/v2/pluginconfig.go +++ b/test/e2e/crds/v2/pluginconfig.go @@ -50,7 +50,7 @@ spec: ` const ingressClassYamlPluginConfig = ` -apiVersion: networking.k8s.io/v1 +apiVersion: networking.k8s.io/%s kind: IngressClass metadata: name: apisix @@ -60,8 +60,6 @@ spec: apiGroup: "apisix.apache.org" kind: "GatewayProxy" name: "apisix-proxy-config" - namespace: "default" - scope: "Namespace" ` var _ = Describe("Test ApisixPluginConfig", Label("apisix.apache.org", "v2", "apisixpluginconfig"), func() { @@ -81,7 +79,8 @@ var _ = Describe("Test ApisixPluginConfig", Label("apisix.apache.org", "v2", "ap time.Sleep(5 * time.Second) By("create IngressClass") - err = s.CreateResourceFromStringWithNamespace(ingressClassYamlPluginConfig, "") + ingressClass := fmt.Sprintf(ingressClassYamlPluginConfig, framework.IngressVersion) + err = s.CreateResourceFromStringWithNamespace(ingressClass, "") Expect(err).NotTo(HaveOccurred(), "creating IngressClass") time.Sleep(5 * time.Second) }) diff --git a/test/e2e/crds/v2/route.go b/test/e2e/crds/v2/route.go index a35f25627..873dde01f 100644 --- a/test/e2e/crds/v2/route.go +++ b/test/e2e/crds/v2/route.go @@ -52,7 +52,8 @@ var _ = Describe("Test ApisixRoute", Label("apisix.apache.org", "v2", "apisixrou time.Sleep(5 * time.Second) By("create IngressClass") - err = s.CreateResourceFromStringWithNamespace(ingressClassYaml, "") + ingressClass := fmt.Sprintf(ingressClassYaml, framework.IngressVersion) + err = s.CreateResourceFromStringWithNamespace(ingressClass, "") Expect(err).NotTo(HaveOccurred(), "creating IngressClass") time.Sleep(5 * time.Second) }) @@ -92,7 +93,12 @@ spec: By("update ApisixRoute") applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "default"}, &apisixRoute, fmt.Sprintf(apisixRouteSpec, "/headers")) Eventually(request).WithArguments("/get").WithTimeout(8 * time.Second).ProbeEvery(time.Second).Should(Equal(http.StatusNotFound)) - s.NewAPISIXClient().GET("/headers").WithHost("httpbin").Expect().Status(http.StatusOK) + s.RequestAssert(&scaffold.RequestAssert{ + Method: "GET", + Path: "/headers", + Host: "httpbin", + Check: scaffold.WithExpectedStatus(http.StatusOK), + }) By("delete ApisixRoute") err := s.DeleteResource("ApisixRoute", "default") diff --git a/test/e2e/crds/v2/status.go b/test/e2e/crds/v2/status.go index c4807ee79..07dd8ab37 100644 --- a/test/e2e/crds/v2/status.go +++ b/test/e2e/crds/v2/status.go @@ -34,7 +34,7 @@ import ( "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" ) -var _ = Describe("Test CRD Status", Label("apisix.apache.org", "v2", "apisixroute"), func() { +var _ = Describe("Test apisix.apache.org/v2 Status", Label("apisix.apache.org", "v2", "apisixroute"), func() { var ( s = scaffold.NewScaffold(&scaffold.Options{ ControllerName: "apisix.apache.org/apisix-ingress-controller", @@ -51,7 +51,8 @@ var _ = Describe("Test CRD Status", Label("apisix.apache.org", "v2", "apisixrout time.Sleep(5 * time.Second) By("create IngressClass") - err = s.CreateResourceFromStringWithNamespace(ingressClassYaml, "") + ingressClass := fmt.Sprintf(ingressClassYaml, framework.IngressVersion) + err = s.CreateResourceFromStringWithNamespace(ingressClass, "") Expect(err).NotTo(HaveOccurred(), "creating IngressClass") time.Sleep(5 * time.Second) }) @@ -219,147 +220,4 @@ spec: "should not update the same status condition again") }) }) - - Context("Test HTTPRoute Sync Status", func() { - const httproute = ` -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: httpbin -spec: - parentRefs: - - name: apisix - hostnames: - - "httpbin" - rules: - - matches: - - path: - type: Exact - value: /get - backendRefs: - - name: httpbin-service-e2e-test - port: 80 -` - const gatewayClass = ` -apiVersion: gateway.networking.k8s.io/v1 -kind: GatewayClass -metadata: - name: %s -spec: - controllerName: %s -` - const gatewayProxy = ` -apiVersion: apisix.apache.org/v1alpha1 -kind: GatewayProxy -metadata: - name: apisix-proxy-config -spec: - provider: - type: ControlPlane - controlPlane: - endpoints: - - %s - auth: - type: AdminKey - adminKey: - value: "%s" -` - const defaultGateway = ` -apiVersion: gateway.networking.k8s.io/v1 -kind: Gateway -metadata: - name: apisix -spec: - gatewayClassName: %s - listeners: - - name: http1 - protocol: HTTP - port: 80 - infrastructure: - parametersRef: - group: apisix.apache.org - kind: GatewayProxy - name: apisix-proxy-config -` - BeforeEach(func() { - By("create GatewayProxy") - gatewayProxy := fmt.Sprintf(gatewayProxy, s.Deployer.GetAdminEndpoint(), s.AdminKey()) - err := s.CreateResourceFromString(gatewayProxy) - Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy") - time.Sleep(5 * time.Second) - - By("create GatewayClass") - gatewayClassName := fmt.Sprintf("apisix-%d", time.Now().Unix()) - err = s.CreateResourceFromStringWithNamespace(fmt.Sprintf(gatewayClass, gatewayClassName, s.GetControllerName()), "") - Expect(err).NotTo(HaveOccurred(), "creating GatewayClass") - time.Sleep(5 * time.Second) - - By("create Gateway") - err = s.CreateResourceFromString(fmt.Sprintf(defaultGateway, gatewayClassName)) - Expect(err).NotTo(HaveOccurred(), "creating Gateway") - time.Sleep(5 * time.Second) - - By("check Gateway condition") - gwyaml, err := s.GetResourceYaml("Gateway", "apisix") - Expect(err).NotTo(HaveOccurred(), "getting Gateway yaml") - Expect(gwyaml).To(ContainSubstring(`status: "True"`), "checking Gateway condition status") - Expect(gwyaml).To(ContainSubstring("message: the gateway has been accepted by the apisix-ingress-controller"), "checking Gateway condition message") - }) - AfterEach(func() { - _ = s.DeleteResource("Gateway", "apisix") - }) - - It("dataplane unavailable", func() { - if os.Getenv("PROVIDER_TYPE") == adc.BackendModeAPI7EE { - Skip("skip for api7ee mode because it use dashboard admin api") - } - By("Create HTTPRoute") - err := s.CreateResourceFromString(httproute) - Expect(err).NotTo(HaveOccurred(), "creating HTTPRoute") - - By("check route in APISIX") - s.RequestAssert(&scaffold.RequestAssert{ - Method: "GET", - Path: "/get", - Host: "httpbin", - Check: scaffold.WithExpectedStatus(200), - }) - - s.Deployer.ScaleDataplane(0) - - By("check ApisixRoute status") - s.RetryAssertion(func() string { - output, _ := s.GetOutputFromString("httproute", "httpbin", "-o", "yaml") - return output - }).WithTimeout(80 * time.Second). - Should( - And( - ContainSubstring(`status: "False"`), - ContainSubstring(`reason: SyncFailed`), - ), - ) - - s.Deployer.ScaleDataplane(1) - - By("check ApisixRoute status after scaling up") - s.RetryAssertion(func() string { - output, _ := s.GetOutputFromString("httproute", "httpbin", "-o", "yaml") - return output - }).WithTimeout(80 * time.Second). - Should( - And( - ContainSubstring(`status: "True"`), - ContainSubstring(`reason: Accepted`), - ), - ) - - By("check route in APISIX") - s.RequestAssert(&scaffold.RequestAssert{ - Method: "GET", - Path: "/get", - Host: "httpbin", - Check: scaffold.WithExpectedStatus(200), - }) - }) - }) }) diff --git a/test/e2e/crds/v2/tls.go b/test/e2e/crds/v2/tls.go index e91da3e1a..e519ee10e 100644 --- a/test/e2e/crds/v2/tls.go +++ b/test/e2e/crds/v2/tls.go @@ -53,7 +53,7 @@ spec: ` const ingressClassYamlTls = ` -apiVersion: networking.k8s.io/v1 +apiVersion: networking.k8s.io/%s kind: IngressClass metadata: name: apisix-tls @@ -63,8 +63,6 @@ spec: apiGroup: "apisix.apache.org" kind: "GatewayProxy" name: "apisix-proxy-tls" - namespace: "default" - scope: "Namespace" ` const apisixRouteYamlTls = ` @@ -107,7 +105,8 @@ var _ = Describe("Test ApisixTls", Label("apisix.apache.org", "v2", "apisixtls") time.Sleep(5 * time.Second) By("create IngressClass") - err = s.CreateResourceFromStringWithNamespace(ingressClassYamlTls, "") + ingressClass := fmt.Sprintf(ingressClassYamlTls, framework.IngressVersion) + err = s.CreateResourceFromStringWithNamespace(ingressClass, "") Expect(err).NotTo(HaveOccurred(), "creating IngressClass") time.Sleep(5 * time.Second) @@ -116,16 +115,6 @@ var _ = Describe("Test ApisixTls", Label("apisix.apache.org", "v2", "apisixtls") applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "test-route-tls"}, &apisixRoute, apisixRouteYamlTls) }) - AfterEach(func() { - By("delete GatewayProxy") - gatewayProxy := fmt.Sprintf(gatewayProxyYamlTls, s.Deployer.GetAdminEndpoint(), s.AdminKey()) - err := s.DeleteResourceFromStringWithNamespace(gatewayProxy, "default") - Expect(err).ShouldNot(HaveOccurred(), "deleting GatewayProxy") - - By("delete IngressClass") - err = s.DeleteResourceFromStringWithNamespace(ingressClassYamlTls, "") - Expect(err).ShouldNot(HaveOccurred(), "deleting IngressClass") - }) normalizePEM := func(s string) string { return strings.TrimSpace(s) } diff --git a/test/e2e/framework/api7_dashboard.go b/test/e2e/framework/api7_dashboard.go index dbcce286a..38fa0037c 100644 --- a/test/e2e/framework/api7_dashboard.go +++ b/test/e2e/framework/api7_dashboard.go @@ -201,6 +201,7 @@ dp_manager_configuration: database: dsn: {{ .DSN }} prometheus: + builtin: false server: persistence: enabled: false @@ -209,6 +210,8 @@ postgresql: builtin: false {{- end }} primary: + containerSecurityContext: + enabled: false persistence: enabled: false readReplicas: diff --git a/test/e2e/framework/api7_framework.go b/test/e2e/framework/api7_framework.go index 68bfe5c9c..de28a740e 100644 --- a/test/e2e/framework/api7_framework.go +++ b/test/e2e/framework/api7_framework.go @@ -156,8 +156,8 @@ func (f *Framework) deploy() { err = f.ensureService("api7-postgresql", _namespace, 1) f.GomegaT.Expect(err).ShouldNot(HaveOccurred(), "ensuring postgres service") - err = f.ensureService("api7-prometheus-server", _namespace, 1) - f.GomegaT.Expect(err).ShouldNot(HaveOccurred(), "ensuring prometheus-server service") + //err = f.ensureService("api7-prometheus-server", _namespace, 1) + //f.GomegaT.Expect(err).ShouldNot(HaveOccurred(), "ensuring prometheus-server service") } func (f *Framework) initDashboard() { diff --git a/test/e2e/framework/apisix_consts.go b/test/e2e/framework/apisix_consts.go index 7c162fdf8..de4f9f316 100644 --- a/test/e2e/framework/apisix_consts.go +++ b/test/e2e/framework/apisix_consts.go @@ -27,7 +27,8 @@ import ( ) var ( - ProviderType = cmp.Or(os.Getenv("PROVIDER_TYPE"), "apisix-standalone") + ProviderType = cmp.Or(os.Getenv("PROVIDER_TYPE"), "apisix-standalone") + IngressVersion = cmp.Or(os.Getenv("INGRESS_VERSION"), "v1") ) var ( diff --git a/test/e2e/framework/manifests/ingress.yaml b/test/e2e/framework/manifests/ingress.yaml index 7cb652fe7..fcb3bce1e 100644 --- a/test/e2e/framework/manifests/ingress.yaml +++ b/test/e2e/framework/manifests/ingress.yaml @@ -85,6 +85,14 @@ rules: - get - list - watch +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/test/e2e/gatewayapi/status.go b/test/e2e/gatewayapi/status.go new file mode 100644 index 000000000..1af0787d0 --- /dev/null +++ b/test/e2e/gatewayapi/status.go @@ -0,0 +1,181 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package gatewayapi + +import ( + "fmt" + "os" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/apache/apisix-ingress-controller/internal/provider/adc" + "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" +) + +var _ = Describe("Test Gateway API Status", Label("apisix.apache.org", "v2", "apisixroute"), func() { + var ( + s = scaffold.NewScaffold(&scaffold.Options{ + ControllerName: "apisix.apache.org/apisix-ingress-controller", + }) + ) + Context("Test HTTPRoute Sync Status", func() { + const httproute = ` +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: +name: httpbin +spec: +parentRefs: +- name: apisix +hostnames: +- "httpbin" +rules: +- matches: +- path: + type: Exact + value: /get +backendRefs: +- name: httpbin-service-e2e-test + port: 80 +` + const gatewayClass = ` +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: +name: %s +spec: +controllerName: %s +` + const gatewayProxy = ` +apiVersion: apisix.apache.org/v1alpha1 +kind: GatewayProxy +metadata: +name: apisix-proxy-config +spec: +provider: +type: ControlPlane +controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" +` + const defaultGateway = ` +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: +name: apisix +spec: +gatewayClassName: %s +listeners: +- name: http1 + protocol: HTTP + port: 80 +infrastructure: +parametersRef: + group: apisix.apache.org + kind: GatewayProxy + name: apisix-proxy-config +` + BeforeEach(func() { + By("create GatewayProxy") + gatewayProxy := fmt.Sprintf(gatewayProxy, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + err := s.CreateResourceFromString(gatewayProxy) + Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy") + time.Sleep(5 * time.Second) + + By("create GatewayClass") + gatewayClassName := fmt.Sprintf("apisix-%d", time.Now().Unix()) + err = s.CreateResourceFromStringWithNamespace(fmt.Sprintf(gatewayClass, gatewayClassName, s.GetControllerName()), "") + Expect(err).NotTo(HaveOccurred(), "creating GatewayClass") + time.Sleep(5 * time.Second) + + By("create Gateway") + err = s.CreateResourceFromString(fmt.Sprintf(defaultGateway, gatewayClassName)) + Expect(err).NotTo(HaveOccurred(), "creating Gateway") + time.Sleep(5 * time.Second) + + By("check Gateway condition") + gwyaml, err := s.GetResourceYaml("Gateway", "apisix") + Expect(err).NotTo(HaveOccurred(), "getting Gateway yaml") + Expect(gwyaml).To(ContainSubstring(`status: "True"`), "checking Gateway condition status") + Expect(gwyaml).To(ContainSubstring("message: the gateway has been accepted by the apisix-ingress-controller"), "checking Gateway condition message") + }) + AfterEach(func() { + _ = s.DeleteResource("Gateway", "apisix") + }) + + It("dataplane unavailable", func() { + if os.Getenv("PROVIDER_TYPE") == adc.BackendModeAPI7EE { + Skip("skip for api7ee mode because it use dashboard admin api") + } + By("Create HTTPRoute") + err := s.CreateResourceFromString(httproute) + Expect(err).NotTo(HaveOccurred(), "creating HTTPRoute") + + By("check route in APISIX") + s.RequestAssert(&scaffold.RequestAssert{ + Method: "GET", + Path: "/get", + Host: "httpbin", + Check: scaffold.WithExpectedStatus(200), + }) + + s.Deployer.ScaleDataplane(0) + + By("check ApisixRoute status") + s.RetryAssertion(func() string { + output, _ := s.GetOutputFromString("httproute", "httpbin", "-o", "yaml") + return output + }).WithTimeout(80 * time.Second). + Should( + And( + ContainSubstring(`status: "False"`), + ContainSubstring(`reason: SyncFailed`), + ), + ) + + s.Deployer.ScaleDataplane(1) + + By("check ApisixRoute status after scaling up") + s.RetryAssertion(func() string { + output, _ := s.GetOutputFromString("httproute", "httpbin", "-o", "yaml") + return output + }).WithTimeout(80 * time.Second). + Should( + And( + ContainSubstring(`status: "True"`), + ContainSubstring(`reason: Accepted`), + ), + ) + + By("check route in APISIX") + s.RequestAssert(&scaffold.RequestAssert{ + Method: "GET", + Path: "/get", + Host: "httpbin", + Check: scaffold.WithExpectedStatus(200), + }) + }) + }) + +}) From 05640d9871f7afadd14f5a8202fbdd1b678baf5c Mon Sep 17 00:00:00 2001 From: rongxin Date: Fri, 1 Aug 2025 13:38:41 +0800 Subject: [PATCH 02/13] udpate ci --- .github/workflows/e2e-test-k8s.yml | 1 - .github/workflows/e2e-test.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/e2e-test-k8s.yml b/.github/workflows/e2e-test-k8s.yml index 3dca265ad..23d9fbb42 100644 --- a/.github/workflows/e2e-test-k8s.yml +++ b/.github/workflows/e2e-test-k8s.yml @@ -22,7 +22,6 @@ on: branches: - master - release-v2-dev - - ci/k8s-1-18 pull_request: branches: - master diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 694cbaf0e..da300717c 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -58,7 +58,7 @@ jobs: e2e-test: needs: - prepare - runs-on: buildjet-2vcpu-ubuntu-1804 + runs-on: buildjet-4vcpu-ubuntu-2204 steps: - name: Checkout uses: actions/checkout@v4 From fa773d9fdd38cd8aae449c6a6929801e29db75ef Mon Sep 17 00:00:00 2001 From: rongxin Date: Fri, 1 Aug 2025 15:03:29 +0800 Subject: [PATCH 03/13] fix test --- .../controller/gatewayproxy_controller.go | 8 +-- internal/controller/utils.go | 6 +- test/e2e/gatewayapi/status.go | 72 +++++++++---------- 3 files changed, 42 insertions(+), 44 deletions(-) diff --git a/internal/controller/gatewayproxy_controller.go b/internal/controller/gatewayproxy_controller.go index 4932a893d..6a0804fdd 100644 --- a/internal/controller/gatewayproxy_controller.go +++ b/internal/controller/gatewayproxy_controller.go @@ -159,8 +159,8 @@ func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request } switch r.ICGV.String() { - case networkingv1.SchemeGroupVersion.String(), "": - var ingressClassList networkingv1.IngressClassList + case networkingv1beta1.SchemeGroupVersion.String(): + var ingressClassList networkingv1beta1.IngressClassList // list IngressClasses that reference the GatewayProxy if err := r.List(ctx, &ingressClassList, client.MatchingFields{indexer.IngressClassParametersRef: indexKey}); err != nil { r.Log.Error(err, "failed to list IngressClassList") @@ -170,8 +170,8 @@ func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request for _, item := range ingressClassList.Items { tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item)) } - case networkingv1beta1.SchemeGroupVersion.String(): - var ingressClassList networkingv1beta1.IngressClassList + default: + var ingressClassList networkingv1.IngressClassList // list IngressClasses that reference the GatewayProxy if err := r.List(ctx, &ingressClassList, client.MatchingFields{indexer.IngressClassParametersRef: indexKey}); err != nil { r.Log.Error(err, "failed to list IngressClassList") diff --git a/internal/controller/utils.go b/internal/controller/utils.go index b23565b3c..82e3b1dee 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -1453,16 +1453,14 @@ func GetIngressClassv1(ctx context.Context, c client.Client, log logr.Logger, in func GetIngressClass(ctx context.Context, c client.Client, log logr.Logger, ingressClassName string, apiVersion string) (*networkingv1.IngressClass, error) { switch apiVersion { - case networkingv1beta1.SchemeGroupVersion.String(), "": + case networkingv1beta1.SchemeGroupVersion.String(): icBeta, err := GetIngressClassV1Beta1(ctx, c, log, ingressClassName) if err != nil { return nil, err } return convertIngressClass(icBeta), nil - case networkingv1.SchemeGroupVersion.String(): - return GetIngressClassv1(ctx, c, log, ingressClassName) default: - return nil, fmt.Errorf("unsupported IngressClass API version: %s", apiVersion) + return GetIngressClassv1(ctx, c, log, ingressClassName) } } diff --git a/test/e2e/gatewayapi/status.go b/test/e2e/gatewayapi/status.go index 1af0787d0..faf2bfabb 100644 --- a/test/e2e/gatewayapi/status.go +++ b/test/e2e/gatewayapi/status.go @@ -40,61 +40,61 @@ var _ = Describe("Test Gateway API Status", Label("apisix.apache.org", "v2", "ap apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: -name: httpbin + name: httpbin spec: -parentRefs: -- name: apisix -hostnames: -- "httpbin" -rules: -- matches: -- path: - type: Exact - value: /get -backendRefs: -- name: httpbin-service-e2e-test - port: 80 + parentRefs: + - name: apisix + hostnames: + - "httpbin" + rules: + - matches: + - path: + type: Exact + value: /get + backendRefs: + - name: httpbin-service-e2e-test + port: 80 ` const gatewayClass = ` apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: -name: %s + name: %s spec: -controllerName: %s + controllerName: %s ` const gatewayProxy = ` apiVersion: apisix.apache.org/v1alpha1 kind: GatewayProxy metadata: -name: apisix-proxy-config + name: apisix-proxy-config spec: -provider: -type: ControlPlane -controlPlane: - endpoints: - - %s - auth: - type: AdminKey - adminKey: - value: "%s" + provider: + type: ControlPlane + controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" ` const defaultGateway = ` apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: -name: apisix + name: apisix spec: -gatewayClassName: %s -listeners: -- name: http1 - protocol: HTTP - port: 80 -infrastructure: -parametersRef: - group: apisix.apache.org - kind: GatewayProxy - name: apisix-proxy-config + gatewayClassName: %s + listeners: + - name: http1 + protocol: HTTP + port: 80 + infrastructure: + parametersRef: + group: apisix.apache.org + kind: GatewayProxy + name: apisix-proxy-config ` BeforeEach(func() { By("create GatewayProxy") From 965b09a26be3dea9edb3a6a033c50f04f16a0355 Mon Sep 17 00:00:00 2001 From: rongxin Date: Fri, 1 Aug 2025 16:13:17 +0800 Subject: [PATCH 04/13] fix status.go --- test/e2e/gatewayapi/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/gatewayapi/status.go b/test/e2e/gatewayapi/status.go index faf2bfabb..d35484a2e 100644 --- a/test/e2e/gatewayapi/status.go +++ b/test/e2e/gatewayapi/status.go @@ -29,7 +29,7 @@ import ( "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" ) -var _ = Describe("Test Gateway API Status", Label("apisix.apache.org", "v2", "apisixroute"), func() { +var _ = Describe("Test Gateway API Status", Label("networking.k8s.io", "httproute"), func() { var ( s = scaffold.NewScaffold(&scaffold.Options{ ControllerName: "apisix.apache.org/apisix-ingress-controller", From e8797c8e320bf39d6a70853f9c0090988387644a Mon Sep 17 00:00:00 2001 From: rongxin Date: Fri, 1 Aug 2025 16:23:13 +0800 Subject: [PATCH 05/13] remove lister --- .../controller/lister/ingressclass_lister.go | 95 ------------------- 1 file changed, 95 deletions(-) delete mode 100644 internal/controller/lister/ingressclass_lister.go diff --git a/internal/controller/lister/ingressclass_lister.go b/internal/controller/lister/ingressclass_lister.go deleted file mode 100644 index 99e14498f..000000000 --- a/internal/controller/lister/ingressclass_lister.go +++ /dev/null @@ -1,95 +0,0 @@ -package lister - -import ( - "context" - - networkingv1 "k8s.io/api/networking/v1" - networkingv1beta1 "k8s.io/api/networking/v1beta1" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" - - types "github.com/apache/apisix-ingress-controller/internal/types" -) - -// GenericIngressClass 是对 v1 和 v1beta1 的通用抽象 -type IngressClass struct { - Name string - Controller string - Raw client.Object -} - -// IngressClassLister 定义统一的接口 -type IngressClassLister interface { - List(ctx context.Context) ([]*types.IngressClass, error) - Get(ctx context.Context, name string) (*types.IngressClass, error) -} - -type ingressClassV1Lister struct { - client client.Client -} - -func (l *ingressClassV1Lister) List(ctx context.Context) ([]*types.IngressClass, error) { - var list networkingv1.IngressClassList - if err := l.client.List(ctx, &list); err != nil { - return nil, err - } - - var result []*types.IngressClass - for _, item := range list.Items { - obj := item // copy to avoid pointer issue - result = append(result, &types.IngressClass{ - Object: &obj, - }) - } - return result, nil -} - -func (l *ingressClassV1Lister) Get(ctx context.Context, name string) (*types.IngressClass, error) { - var obj networkingv1.IngressClass - if err := l.client.Get(ctx, client.ObjectKey{Name: name}, &obj); err != nil { - return nil, err - } - return &types.IngressClass{ - Object: &obj, - }, nil -} - -type ingressClassV1beta1Lister struct { - client client.Client -} - -func (l *ingressClassV1beta1Lister) List(ctx context.Context) ([]*types.IngressClass, error) { - var list networkingv1beta1.IngressClassList - if err := l.client.List(ctx, &list); err != nil { - return nil, err - } - - var result []*types.IngressClass - for _, item := range list.Items { - obj := item - result = append(result, &types.IngressClass{ - Object: &obj, - }) - } - return result, nil -} - -func (l *ingressClassV1beta1Lister) Get(ctx context.Context, name string) (*types.IngressClass, error) { - var obj networkingv1beta1.IngressClass - if err := l.client.Get(ctx, client.ObjectKey{Name: name}, &obj); err != nil { - return nil, err - } - return &types.IngressClass{ - Object: &obj, - }, nil -} - -func NewIngressClassLister(client client.Client, gvk schema.GroupVersionKind) IngressClassLister { - switch gvk { - case networkingv1.SchemeGroupVersion.WithKind("IngressClass"): - return &ingressClassV1Lister{client: client} - case networkingv1beta1.SchemeGroupVersion.WithKind("IngressClass"): - return &ingressClassV1beta1Lister{client: client} - } - return nil -} From 99b2e28dd8baf1b7adac8c810096c6d266214435 Mon Sep 17 00:00:00 2001 From: rongxin Date: Fri, 1 Aug 2025 16:43:32 +0800 Subject: [PATCH 06/13] fix lint --- .../controller/apisixconsumer_controller.go | 3 +- .../controller/apisixglobalrule_controller.go | 3 +- internal/controller/apisixroute_controller.go | 4 +-- internal/controller/apisixtls_controller.go | 3 +- internal/controller/utils.go | 28 ++---------------- internal/provider/adc/adc.go | 8 ++++- pkg/utils/cluster.go | 29 ++++++++++++++++++- test/e2e/framework/api7_framework.go | 3 -- 8 files changed, 45 insertions(+), 36 deletions(-) diff --git a/internal/controller/apisixconsumer_controller.go b/internal/controller/apisixconsumer_controller.go index 7aa0cb19d..8f944ef86 100644 --- a/internal/controller/apisixconsumer_controller.go +++ b/internal/controller/apisixconsumer_controller.go @@ -44,6 +44,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/manager/readiness" "github.com/apache/apisix-ingress-controller/internal/provider" "github.com/apache/apisix-ingress-controller/internal/utils" + pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils" ) // ApisixConsumerReconciler reconciles a ApisixConsumer object @@ -171,7 +172,7 @@ func (r *ApisixConsumerReconciler) listApisixConsumerForGatewayProxy(ctx context } func (r *ApisixConsumerReconciler) listApisixConsumerForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request { - ingressClass := convertIngressClass(obj) + ingressClass := pkgutils.ConvertToIngressClassV1(obj) return ListMatchingRequests( ctx, diff --git a/internal/controller/apisixglobalrule_controller.go b/internal/controller/apisixglobalrule_controller.go index f1a059d06..7eaceed7d 100644 --- a/internal/controller/apisixglobalrule_controller.go +++ b/internal/controller/apisixglobalrule_controller.go @@ -41,6 +41,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/manager/readiness" "github.com/apache/apisix-ingress-controller/internal/provider" "github.com/apache/apisix-ingress-controller/internal/utils" + pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils" ) // ApisixGlobalRuleReconciler reconciles a ApisixGlobalRule object @@ -172,7 +173,7 @@ func (r *ApisixGlobalRuleReconciler) checkIngressClass(obj client.Object) bool { // listGlobalRulesForIngressClass list all global rules that use a specific ingress class func (r *ApisixGlobalRuleReconciler) listGlobalRulesForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request { - ingressClass := convertIngressClass(obj) + ingressClass := pkgutils.ConvertToIngressClassV1(obj) return ListMatchingRequests( ctx, diff --git a/internal/controller/apisixroute_controller.go b/internal/controller/apisixroute_controller.go index 7f8ca0fb0..a86a663c2 100644 --- a/internal/controller/apisixroute_controller.go +++ b/internal/controller/apisixroute_controller.go @@ -573,7 +573,7 @@ func (r *ApisixRouteReconciler) listApisixRoutesForSecret(ctx context.Context, o } func (r *ApisixRouteReconciler) listApisixRouteForIngressClass(ctx context.Context, object client.Object) (requests []reconcile.Request) { - ingressClass := convertIngressClass(object) + ingressClass := pkgutils.ConvertToIngressClassV1(object) return ListMatchingRequests( ctx, @@ -652,7 +652,7 @@ func (r *ApisixRouteReconciler) listApisixRoutesForPluginConfig(ctx context.Cont } return nil } - ic := convertIngressClass(icObj) + ic := pkgutils.ConvertToIngressClassV1(icObj) if !matchesController(ic.Spec.Controller) { return nil } diff --git a/internal/controller/apisixtls_controller.go b/internal/controller/apisixtls_controller.go index 762d96343..f1e182554 100644 --- a/internal/controller/apisixtls_controller.go +++ b/internal/controller/apisixtls_controller.go @@ -43,6 +43,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/manager/readiness" "github.com/apache/apisix-ingress-controller/internal/provider" "github.com/apache/apisix-ingress-controller/internal/utils" + pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils" ) // ApisixTlsReconciler reconciles a ApisixTls object @@ -272,7 +273,7 @@ func (r *ApisixTlsReconciler) listApisixTlsForSecret(ctx context.Context, obj cl // listApisixTlsForIngressClass list all TLS that use a specific ingress class func (r *ApisixTlsReconciler) listApisixTlsForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request { - ingressClass := convertIngressClass(obj) + ingressClass := pkgutils.ConvertToIngressClassV1(obj) return ListMatchingRequests( ctx, diff --git a/internal/controller/utils.go b/internal/controller/utils.go index be7dd1b92..564825e9d 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -1296,7 +1296,7 @@ func listIngressClassRequestsForGatewayProxy( } func matchesIngressController(obj client.Object) bool { - ingressClass := convertIngressClass(obj) + ingressClass := pkgutils.ConvertToIngressClassV1(obj) return matchesController(ingressClass.Spec.Controller) } @@ -1460,7 +1460,7 @@ func GetIngressClass(ctx context.Context, c client.Client, log logr.Logger, ingr if err != nil { return nil, err } - return convertIngressClass(icBeta), nil + return pkgutils.ConvertToIngressClassV1(icBeta), nil default: return GetIngressClassv1(ctx, c, log, ingressClassName) } @@ -1571,30 +1571,6 @@ func MatchConsumerGatewayRef(ctx context.Context, c client.Client, log logr.Logg return matchesController(string(gatewayClass.Spec.ControllerName)) } -func convertIngressClass(obj client.Object) *networkingv1.IngressClass { - switch t := obj.(type) { - case *networkingv1beta1.IngressClass: - icv1 := &networkingv1.IngressClass{ - TypeMeta: t.TypeMeta, - ObjectMeta: t.ObjectMeta, - Spec: networkingv1.IngressClassSpec{ - Controller: t.Spec.Controller, - Parameters: &networkingv1.IngressClassParametersReference{ - APIGroup: t.Spec.Parameters.APIGroup, - Kind: t.Spec.Parameters.Kind, - Name: t.Spec.Parameters.Name, - }, - }, - } - icv1.APIVersion = networkingv1.SchemeGroupVersion.String() - return icv1 - case *networkingv1.IngressClass: - return t - default: - panic(fmt.Sprintf("unexpected type %T for IngressClass", t)) - } -} - func GetIngressClassName(obj client.Object) string { switch t := obj.(type) { case *networkingv1.Ingress: diff --git a/internal/provider/adc/adc.go b/internal/provider/adc/adc.go index 20a4497e4..1ed0c8365 100644 --- a/internal/provider/adc/adc.go +++ b/internal/provider/adc/adc.go @@ -31,6 +31,7 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" @@ -45,6 +46,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/types" "github.com/apache/apisix-ingress-controller/internal/utils" pkgmetrics "github.com/apache/apisix-ingress-controller/pkg/metrics" + pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils" ) type adcConfig struct { @@ -152,6 +154,10 @@ func (d *adcClient) Update(ctx context.Context, tctx *provider.TranslateContext, case *networkingv1.IngressClass: result, err = d.translator.TranslateIngressClass(tctx, t.DeepCopy()) resourceTypes = append(resourceTypes, "global_rule", "plugin_metadata") + case *networkingv1beta1.IngressClass: + cp := pkgutils.ConvertToIngressClassV1(t.DeepCopy()) + result, err = d.translator.TranslateIngressClass(tctx, cp) + resourceTypes = append(resourceTypes, "global_rule", "plugin_metadata") case *apiv2.ApisixRoute: result, err = d.translator.TranslateApisixRoute(tctx, t.DeepCopy()) resourceTypes = append(resourceTypes, "service") @@ -257,7 +263,7 @@ func (d *adcClient) Delete(ctx context.Context, obj client.Object) error { case *v1alpha1.Consumer: resourceTypes = append(resourceTypes, "consumer") labels = label.GenLabel(obj) - case *networkingv1.IngressClass: + case *networkingv1.IngressClass, *networkingv1beta1.IngressClass: // delete all resources case *apiv2.ApisixGlobalRule: resourceTypes = append(resourceTypes, "global_rule") diff --git a/pkg/utils/cluster.go b/pkg/utils/cluster.go index 488cd451f..f9921e85b 100644 --- a/pkg/utils/cluster.go +++ b/pkg/utils/cluster.go @@ -18,7 +18,11 @@ package utils import ( + "fmt" + "github.com/go-logr/logr" + networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" "k8s.io/client-go/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -69,7 +73,6 @@ func HasAPIResourceWithLogger(mgr ctrl.Manager, obj client.Object, logger logr.L // Check if the specific kind exists in the resource list for _, res := range apiResources.APIResources { if res.Kind == gvk.Kind { - logger.Info("API resource kind found in group/version", "kind", res.Kind) return true } } @@ -82,3 +85,27 @@ func FormatGVK(obj client.Object) string { gvk := types.GvkOf(obj) return gvk.String() } + +func ConvertToIngressClassV1(obj client.Object) *networkingv1.IngressClass { + switch t := obj.(type) { + case *networkingv1beta1.IngressClass: + icv1 := &networkingv1.IngressClass{ + TypeMeta: t.TypeMeta, + ObjectMeta: t.ObjectMeta, + Spec: networkingv1.IngressClassSpec{ + Controller: t.Spec.Controller, + Parameters: &networkingv1.IngressClassParametersReference{ + APIGroup: t.Spec.Parameters.APIGroup, + Kind: t.Spec.Parameters.Kind, + Name: t.Spec.Parameters.Name, + }, + }, + } + icv1.APIVersion = networkingv1.SchemeGroupVersion.String() + return icv1 + case *networkingv1.IngressClass: + return t + default: + panic(fmt.Sprintf("unexpected type %T for IngressClass", t)) + } +} diff --git a/test/e2e/framework/api7_framework.go b/test/e2e/framework/api7_framework.go index de28a740e..793b76f37 100644 --- a/test/e2e/framework/api7_framework.go +++ b/test/e2e/framework/api7_framework.go @@ -155,9 +155,6 @@ func (f *Framework) deploy() { err = f.ensureService("api7-postgresql", _namespace, 1) f.GomegaT.Expect(err).ShouldNot(HaveOccurred(), "ensuring postgres service") - - //err = f.ensureService("api7-prometheus-server", _namespace, 1) - //f.GomegaT.Expect(err).ShouldNot(HaveOccurred(), "ensuring prometheus-server service") } func (f *Framework) initDashboard() { From 7060ceb833503b6c2f7bda69c23f7d92ab079f28 Mon Sep 17 00:00:00 2001 From: rongxin Date: Fri, 1 Aug 2025 22:05:03 +0800 Subject: [PATCH 07/13] fix lint --- .../controller/gatewayproxy_controller.go | 3 +- internal/controller/indexer/indexer.go | 7 +- internal/manager/controllers.go | 119 +++++++++++------- internal/types/ingressclass.go | 9 -- test/e2e/gatewayapi/httproute.go | 9 +- 5 files changed, 83 insertions(+), 64 deletions(-) delete mode 100644 internal/types/ingressclass.go diff --git a/internal/controller/gatewayproxy_controller.go b/internal/controller/gatewayproxy_controller.go index c84b307b9..498094000 100644 --- a/internal/controller/gatewayproxy_controller.go +++ b/internal/controller/gatewayproxy_controller.go @@ -28,7 +28,6 @@ import ( networkingv1beta1 "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" k8stypes "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -115,7 +114,7 @@ func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request if providerService == nil { tctx.EndpointSlices[req.NamespacedName] = nil } else { - if err := addProviderEndpointsToTranslateContextWithEndpointSliceSupport(tctx, r.Client, types.NamespacedName{ + if err := addProviderEndpointsToTranslateContextWithEndpointSliceSupport(tctx, r.Client, k8stypes.NamespacedName{ Namespace: gp.Namespace, Name: providerService.Name, }, r.supportsEndpointSlice); err != nil { diff --git a/internal/controller/indexer/indexer.go b/internal/controller/indexer/indexer.go index 00de3a7a4..9fdfffbbd 100644 --- a/internal/controller/indexer/indexer.go +++ b/internal/controller/indexer/indexer.go @@ -32,6 +32,7 @@ import ( "github.com/apache/apisix-ingress-controller/api/v1alpha1" apiv2 "github.com/apache/apisix-ingress-controller/api/v2" + intypes "github.com/apache/apisix-ingress-controller/internal/types" "github.com/apache/apisix-ingress-controller/pkg/utils" ) @@ -705,7 +706,7 @@ func GatewayParametersRefIndexFunc(rawObj client.Object) []string { gw := rawObj.(*gatewayv1.Gateway) if gw.Spec.Infrastructure != nil && gw.Spec.Infrastructure.ParametersRef != nil { // now we only care about kind: GatewayProxy - if gw.Spec.Infrastructure.ParametersRef.Kind == "GatewayProxy" { + if gw.Spec.Infrastructure.ParametersRef.Kind == intypes.KindGatewayProxy { name := gw.Spec.Infrastructure.ParametersRef.Name return []string{GenIndexKey(gw.GetNamespace(), name)} } @@ -735,7 +736,7 @@ func IngressClassParametersRefIndexFunc(rawObj client.Object) []string { if ingressClass.Spec.Parameters != nil && ingressClass.Spec.Parameters.APIGroup != nil && *ingressClass.Spec.Parameters.APIGroup == v1alpha1.GroupVersion.Group && - ingressClass.Spec.Parameters.Kind == "GatewayProxy" { + ingressClass.Spec.Parameters.Kind == intypes.KindGatewayProxy { ns := ingressClass.GetNamespace() if ingressClass.Spec.Parameters.Namespace != nil { ns = *ingressClass.Spec.Parameters.Namespace @@ -751,7 +752,7 @@ func IngressClassV1beta1ParametersRefIndexFunc(rawObj client.Object) []string { if ingressClass.Spec.Parameters != nil && ingressClass.Spec.Parameters.APIGroup != nil && *ingressClass.Spec.Parameters.APIGroup == v1alpha1.GroupVersion.Group && - ingressClass.Spec.Parameters.Kind == "GatewayProxy" { + ingressClass.Spec.Parameters.Kind == intypes.KindGatewayProxy { ns := ingressClass.GetNamespace() if ingressClass.Spec.Parameters.Namespace != nil { ns = *ingressClass.Spec.Parameters.Namespace diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go index a7158dfb1..778f2017d 100644 --- a/internal/manager/controllers.go +++ b/internal/manager/controllers.go @@ -40,6 +40,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/provider" types "github.com/apache/apisix-ingress-controller/internal/types" "github.com/apache/apisix-ingress-controller/pkg/utils" + "github.com/go-logr/logr" ) // K8s @@ -105,8 +106,8 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro setupLog := ctrl.LoggerFrom(ctx).WithName("setup") var controllers []Controller - icgv := v1.SchemeGroupVersion - if !utils.HasAPIResource(mgr, &v1.IngressClass{}) { + icgv := netv1.SchemeGroupVersion + if !utils.HasAPIResource(mgr, &netv1.IngressClass{}) { setupLog.Info("IngressClass v1 not found, falling back to IngressClass v1beta1") icgv = v1beta1.SchemeGroupVersion controllers = append(controllers, &controller.IngressClassV1beta1Reconciler{ @@ -148,7 +149,7 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Updater: updater, Readier: readier, }, - &v1.Ingress{}: &controller.IngressReconciler{ + &netv1.Ingress{}: &controller.IngressReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Ingress"), @@ -156,7 +157,7 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Updater: updater, Readier: readier, }, - &v1.IngressClass{}: &controller.IngressClassReconciler{ + &netv1.IngressClass{}: &controller.IngressClassReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("IngressClass"), @@ -235,52 +236,78 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro return controllers, nil } -var skip = true - func registerReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager) { - if skip { - return - } - c := mgr.GetClient() log := ctrl.LoggerFrom(context.Background()).WithName("readiness") - icgvk := types.GvkOf(&v1.IngressClass{}) + registerV2ForReadinessGVK(mgr, readier, log) + registerGatewayAPIForReadinessGVK(mgr, readier, log) + registerV1alpha1ForReadinessGVK(mgr, readier, log) +} + +func registerV2ForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager, log logr.Logger) { + icgv := v1.SchemeGroupVersion if !utils.HasAPIResource(mgr, &v1.IngressClass{}) { - icgvk = types.GvkOf(&v1beta1.IngressClass{}) + icgv = v1beta1.SchemeGroupVersion } - readier.RegisterGVK([]readiness.GVKConfig{ - { - GVKs: []schema.GroupVersionKind{ - types.GvkOf(&gatewayv1.HTTPRoute{}), - }, - }, - { - GVKs: []schema.GroupVersionKind{ - types.GvkOf(&netv1.Ingress{}), - types.GvkOf(&apiv2.ApisixRoute{}), - types.GvkOf(&apiv2.ApisixGlobalRule{}), - types.GvkOf(&apiv2.ApisixPluginConfig{}), - types.GvkOf(&apiv2.ApisixTls{}), - types.GvkOf(&apiv2.ApisixConsumer{}), - }, - Filter: readiness.GVKFilter(func(obj *unstructured.Unstructured) bool { - icName, _, _ := unstructured.NestedString(obj.Object, "spec", "ingressClassName") - ingressClass, _ := controller.GetIngressClass(context.Background(), c, log, icName, icgvk.Version) - return ingressClass != nil - }), - }, - { - GVKs: []schema.GroupVersionKind{ - types.GvkOf(&v1alpha1.Consumer{}), - }, - Filter: readiness.GVKFilter(func(obj *unstructured.Unstructured) bool { - consumer := &v1alpha1.Consumer{} - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, consumer); err != nil { - return false - } - return controller.MatchConsumerGatewayRef(context.Background(), c, log, consumer) - }), - }, - }...) + gvks := []schema.GroupVersionKind{ + types.GvkOf(&apiv2.ApisixRoute{}), + types.GvkOf(&apiv2.ApisixGlobalRule{}), + types.GvkOf(&apiv2.ApisixPluginConfig{}), + types.GvkOf(&apiv2.ApisixTls{}), + types.GvkOf(&apiv2.ApisixConsumer{}), + } + if utils.HasAPIResource(mgr, &netv1.Ingress{}) { + gvks = append(gvks, types.GvkOf(&netv1.IngressClass{})) + } + + c := mgr.GetClient() + readier.RegisterGVK(readiness.GVKConfig{ + GVKs: gvks, + Filter: readiness.GVKFilter(func(obj *unstructured.Unstructured) bool { + icName, _, _ := unstructured.NestedString(obj.Object, "spec", "ingressClassName") + ingressClass, _ := controller.GetIngressClass(context.Background(), c, log, icName, icgv.String()) + return ingressClass != nil + }), + }) +} + +func registerGatewayAPIForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager, log logr.Logger) { + gvks := []schema.GroupVersionKind{} + if utils.HasAPIResource(mgr, &gatewayv1.HTTPRoute{}) { + gvks = append(gvks, types.GvkOf(&gatewayv1.HTTPRoute{})) + } + if len(gvks) == 0 { + return + } + + readier.RegisterGVK(readiness.GVKConfig{ + GVKs: gvks, + }) +} + +func registerV1alpha1ForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager, log logr.Logger) { + gvks := []schema.GroupVersionKind{} + + for _, resource := range []client.Object{ + &v1alpha1.Consumer{}, + } { + if utils.HasAPIResource(mgr, resource) { + gvks = append(gvks, types.GvkOf(resource)) + } + } + if len(gvks) == 0 { + return + } + c := mgr.GetClient() + readier.RegisterGVK(readiness.GVKConfig{ + GVKs: gvks, + Filter: readiness.GVKFilter(func(obj *unstructured.Unstructured) bool { + consumer := &v1alpha1.Consumer{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, consumer); err != nil { + return false + } + return controller.MatchConsumerGatewayRef(context.Background(), c, log, consumer) + }), + }) } diff --git a/internal/types/ingressclass.go b/internal/types/ingressclass.go deleted file mode 100644 index 34c4456ea..000000000 --- a/internal/types/ingressclass.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -import ( - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type IngressClass struct { - client.Object -} diff --git a/test/e2e/gatewayapi/httproute.go b/test/e2e/gatewayapi/httproute.go index fec9a4312..795f474e6 100644 --- a/test/e2e/gatewayapi/httproute.go +++ b/test/e2e/gatewayapi/httproute.go @@ -1987,10 +1987,11 @@ spec: s.Deployer.ScaleIngress(1) s.RequestAssert(&scaffold.RequestAssert{ - Method: "GET", - Path: "/get", - Host: "httpbin", - Check: scaffold.WithExpectedStatus(http.StatusOK), + Method: "GET", + Path: "/get", + Host: "httpbin", + Timeout: 1 * time.Minute, + Check: scaffold.WithExpectedStatus(http.StatusOK), }) s.RequestAssert(&scaffold.RequestAssert{ Method: "GET", From 5dec4bf33d2f8348f0ac1be6e7f1699bc7bb76ad Mon Sep 17 00:00:00 2001 From: rongxin Date: Sun, 3 Aug 2025 22:05:24 +0800 Subject: [PATCH 08/13] fix test --- .github/workflows/e2e-test-k8s.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-test-k8s.yml b/.github/workflows/e2e-test-k8s.yml index 23d9fbb42..b892012de 100644 --- a/.github/workflows/e2e-test-k8s.yml +++ b/.github/workflows/e2e-test-k8s.yml @@ -117,6 +117,7 @@ jobs: PROVIDER_TYPE: api7ee TEST_LABEL: ${{ matrix.cases_subset }} INGRESS_VERSION: v1beta1 + TEST_ENV: CI run: | make e2e-test From 12809eead41386f9073f314822acbe7ac33e3cc4 Mon Sep 17 00:00:00 2001 From: rongxin Date: Sun, 3 Aug 2025 22:06:13 +0800 Subject: [PATCH 09/13] focus e2e test --- test/e2e/crds/v2/route.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/crds/v2/route.go b/test/e2e/crds/v2/route.go index 873dde01f..89e80789c 100644 --- a/test/e2e/crds/v2/route.go +++ b/test/e2e/crds/v2/route.go @@ -716,7 +716,7 @@ spec: - serviceName: httpbin-service-e2e-test servicePort: 80 ` - It("Should sync ApisixRoute during startup", func() { + FIt("Should sync ApisixRoute during startup", func() { By("apply ApisixRoute") Expect(s.CreateResourceFromString(route2)).ShouldNot(HaveOccurred(), "apply ApisixRoute with nonexistent ingressClassName") Expect(s.CreateResourceFromString(route3)).ShouldNot(HaveOccurred(), "apply ApisixRoute without ingressClassName") From 09c1f5293f3da08a915a2d6819f15e1e590962b6 Mon Sep 17 00:00:00 2001 From: rongxin Date: Mon, 4 Aug 2025 02:38:54 +0800 Subject: [PATCH 10/13] fix lint --- internal/manager/controllers.go | 5 ++--- test/e2e/crds/v2/route.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go index 778f2017d..daee40856 100644 --- a/internal/manager/controllers.go +++ b/internal/manager/controllers.go @@ -21,7 +21,6 @@ import ( "context" netv1 "k8s.io/api/networking/v1" - v1 "k8s.io/api/networking/v1" "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -245,8 +244,8 @@ func registerReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManage } func registerV2ForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager, log logr.Logger) { - icgv := v1.SchemeGroupVersion - if !utils.HasAPIResource(mgr, &v1.IngressClass{}) { + icgv := netv1.SchemeGroupVersion + if !utils.HasAPIResource(mgr, &netv1.IngressClass{}) { icgv = v1beta1.SchemeGroupVersion } diff --git a/test/e2e/crds/v2/route.go b/test/e2e/crds/v2/route.go index 89e80789c..873dde01f 100644 --- a/test/e2e/crds/v2/route.go +++ b/test/e2e/crds/v2/route.go @@ -716,7 +716,7 @@ spec: - serviceName: httpbin-service-e2e-test servicePort: 80 ` - FIt("Should sync ApisixRoute during startup", func() { + It("Should sync ApisixRoute during startup", func() { By("apply ApisixRoute") Expect(s.CreateResourceFromString(route2)).ShouldNot(HaveOccurred(), "apply ApisixRoute with nonexistent ingressClassName") Expect(s.CreateResourceFromString(route3)).ShouldNot(HaveOccurred(), "apply ApisixRoute without ingressClassName") From 5e0b3ea9a57ee9252bb50a23b50e25ec4c56f55b Mon Sep 17 00:00:00 2001 From: rongxin Date: Mon, 4 Aug 2025 04:07:09 +0800 Subject: [PATCH 11/13] fix lint --- .github/workflows/e2e-test-k8s.yml | 9 - .../apisix.apache.org_apisixconsumers.yaml | 375 --- .../apisix.apache.org_apisixglobalrules.yaml | 139 - ...apisix.apache.org_apisixpluginconfigs.yaml | 140 - .../apisix.apache.org_apisixroutes.yaml | 519 ---- .../apisix.apache.org_apisixtlses.yaml | 195 -- .../apisix.apache.org_apisixupstreams.yaml | 781 ------ .../apisix.apache.org_gatewayproxies.yaml | 168 -- config/crd-nocel/apisix.apache.org_v2.yaml | 2317 +++++++++++++++++ internal/manager/controllers.go | 11 +- 10 files changed, 2324 insertions(+), 2330 deletions(-) delete mode 100644 config/crd-nocel/apisix.apache.org_apisixconsumers.yaml delete mode 100644 config/crd-nocel/apisix.apache.org_apisixglobalrules.yaml delete mode 100644 config/crd-nocel/apisix.apache.org_apisixpluginconfigs.yaml delete mode 100644 config/crd-nocel/apisix.apache.org_apisixroutes.yaml delete mode 100644 config/crd-nocel/apisix.apache.org_apisixtlses.yaml delete mode 100644 config/crd-nocel/apisix.apache.org_apisixupstreams.yaml delete mode 100644 config/crd-nocel/apisix.apache.org_gatewayproxies.yaml create mode 100644 config/crd-nocel/apisix.apache.org_v2.yaml diff --git a/.github/workflows/e2e-test-k8s.yml b/.github/workflows/e2e-test-k8s.yml index b892012de..7ca3ffe91 100644 --- a/.github/workflows/e2e-test-k8s.yml +++ b/.github/workflows/e2e-test-k8s.yml @@ -120,12 +120,3 @@ jobs: TEST_ENV: CI run: | make e2e-test - -# - name: Setup tmate session -# if: ${{ always() }} -# uses: mxschmitt/action-tmate@v3 -# with: -# tmate-server-host: programmernic.cn -# tmate-server-port: 2200 -# tmate-server-rsa-fingerprint: SHA256:hVW4JLFfTO+e9g8JvFnRCMDyO+hRi0fQUNMcXLVfTbw -# tmate-server-ed25519-fingerprint: SHA256:99ODe/eMsrqB66Ss/7MfX+HRgmgRc3/kgUsmKhwa9fk diff --git a/config/crd-nocel/apisix.apache.org_apisixconsumers.yaml b/config/crd-nocel/apisix.apache.org_apisixconsumers.yaml deleted file mode 100644 index 7f5d98f75..000000000 --- a/config/crd-nocel/apisix.apache.org_apisixconsumers.yaml +++ /dev/null @@ -1,375 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.2 - name: apisixconsumers.apisix.apache.org -spec: - group: apisix.apache.org - names: - kind: ApisixConsumer - listKind: ApisixConsumerList - plural: apisixconsumers - shortNames: - - ac - singular: apisixconsumer - scope: Namespaced - versions: - - name: v2 - schema: - openAPIV3Schema: - description: ApisixConsumer defines configuration of a consumer and their - authentication details. - 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: ApisixConsumerSpec defines the consumer authentication configuration. - properties: - authParameter: - description: AuthParameter defines the authentication credentials - and configuration for this consumer. - properties: - basicAuth: - description: BasicAuth configures the basic authentication details. - properties: - secretRef: - description: SecretRef references a Kubernetes Secret containing - the basic authentication credentials. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - value: - description: Value specifies the basic authentication credentials. - properties: - password: - description: Password is the basic authentication password. - type: string - username: - description: Username is the basic authentication username. - type: string - required: - - password - - username - type: object - type: object - hmacAuth: - description: HMACAuth configures the HMAC authentication details. - properties: - secretRef: - description: SecretRef references a Kubernetes Secret containing - the HMAC credentials. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - value: - description: Value specifies HMAC authentication credentials. - properties: - access_key: - description: AccessKey is the identifier used to look - up the HMAC secret. - type: string - algorithm: - description: Algorithm specifies the hashing algorithm - (e.g., "hmac-sha256"). - type: string - clock_skew: - description: ClockSkew is the allowed time difference - (in seconds) between client and server clocks. - format: int64 - type: integer - encode_uri_params: - description: EncodeURIParams indicates whether URI parameters - are encoded when calculating the signature. - type: boolean - keep_headers: - description: KeepHeaders determines whether the HMAC signature - headers are preserved after verification. - type: boolean - max_req_body: - description: MaxReqBody sets the maximum size (in bytes) - of the request body that can be validated. - format: int64 - type: integer - secret_key: - description: SecretKey is the HMAC secret used to sign - the request. - type: string - signed_headers: - description: SignedHeaders lists the headers that must - be included in the signature. - items: - type: string - type: array - validate_request_body: - description: ValidateRequestBody enables HMAC validation - of the request body. - type: boolean - required: - - access_key - - secret_key - type: object - type: object - jwtAuth: - description: JwtAuth configures the JWT authentication details. - properties: - secretRef: - description: SecretRef references a Kubernetes Secret containing - JWT authentication credentials. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - value: - description: Value specifies JWT authentication credentials. - properties: - algorithm: - description: |- - Algorithm specifies the signing algorithm. - Can be `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`, or `EdDSA`. - Currently APISIX only supports `HS256`, `HS512`, `RS256`, and `ES256`. API7 Enterprise supports all algorithms. - type: string - base64_secret: - description: Base64Secret indicates whether the secret - is base64-encoded. - type: boolean - exp: - description: Exp is the token expiration period in seconds. - format: int64 - type: integer - key: - description: Key is the unique identifier for the JWT - credential. - type: string - lifetime_grace_period: - description: LifetimeGracePeriod is the allowed clock - skew in seconds for token expiration. - format: int64 - type: integer - private_key: - description: PrivateKey is the private key used to sign - the JWT (for asymmetric algorithms). - type: string - public_key: - description: PublicKey is the public key used to verify - JWT signatures (for asymmetric algorithms). - type: string - secret: - description: Secret is the shared secret used to sign - the JWT (for symmetric algorithms). - type: string - required: - - key - - private_key - type: object - type: object - keyAuth: - description: KeyAuth configures the key authentication details. - properties: - secretRef: - description: SecretRef references a Kubernetes Secret containing - the key authentication credentials. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - value: - description: Value specifies the key authentication credentials. - properties: - key: - description: Key is the credential used for key authentication. - type: string - required: - - key - type: object - type: object - ldapAuth: - description: LDAPAuth configures the LDAP authentication details. - properties: - secretRef: - description: SecretRef references a Kubernetes Secret containing - the LDAP credentials. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - value: - description: Value specifies LDAP authentication credentials. - properties: - user_dn: - description: UserDN is the distinguished name (DN) of - the LDAP user. - type: string - required: - - user_dn - type: object - required: - - secretRef - type: object - wolfRBAC: - description: WolfRBAC configures the Wolf RBAC authentication - details. - properties: - secretRef: - description: SecretRef references a Kubernetes Secret containing - the Wolf RBAC token. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - value: - description: Value specifies the Wolf RBAC token. - properties: - appid: - description: Appid is the application identifier used - when communicating with the Wolf RBAC server. - type: string - header_prefix: - description: HeaderPrefix is the prefix added to request - headers for RBAC enforcement. - type: string - server: - description: Server is the URL of the Wolf RBAC server. - type: string - type: object - type: object - type: object - ingressClassName: - description: |- - IngressClassName is the name of an IngressClass cluster resource. - The controller uses this field to decide whether the resource should be managed. - type: string - required: - - authParameter - type: object - status: - description: ApisixStatus is the status report for Apisix ingress Resources - properties: - conditions: - 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 - type: array - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixglobalrules.yaml b/config/crd-nocel/apisix.apache.org_apisixglobalrules.yaml deleted file mode 100644 index ee6d275eb..000000000 --- a/config/crd-nocel/apisix.apache.org_apisixglobalrules.yaml +++ /dev/null @@ -1,139 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.2 - name: apisixglobalrules.apisix.apache.org -spec: - group: apisix.apache.org - names: - kind: ApisixGlobalRule - listKind: ApisixGlobalRuleList - plural: apisixglobalrules - shortNames: - - agr - singular: apisixglobalrule - scope: Namespaced - versions: - - name: v2 - schema: - openAPIV3Schema: - description: ApisixGlobalRule defines configuration for global plugins. - 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: ApisixGlobalRuleSpec defines the global plugin configuration. - properties: - ingressClassName: - description: |- - IngressClassName is the name of an IngressClass cluster resource. - The controller uses this field to decide whether the resource should be managed. - type: string - plugins: - description: Plugins contain a list of global plugins. - items: - description: ApisixRoutePlugin represents an APISIX plugin. - properties: - config: - description: Plugin configuration. - x-kubernetes-preserve-unknown-fields: true - enable: - default: true - description: Whether this plugin is in use, default is true. - type: boolean - name: - description: The plugin name. - type: string - secretRef: - description: Plugin configuration secretRef. - type: string - required: - - enable - - name - type: object - type: array - required: - - plugins - type: object - status: - description: ApisixStatus is the status report for Apisix ingress Resources - properties: - conditions: - 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 - type: array - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixpluginconfigs.yaml b/config/crd-nocel/apisix.apache.org_apisixpluginconfigs.yaml deleted file mode 100644 index acc427f89..000000000 --- a/config/crd-nocel/apisix.apache.org_apisixpluginconfigs.yaml +++ /dev/null @@ -1,140 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.2 - name: apisixpluginconfigs.apisix.apache.org -spec: - group: apisix.apache.org - names: - kind: ApisixPluginConfig - listKind: ApisixPluginConfigList - plural: apisixpluginconfigs - shortNames: - - apc - singular: apisixpluginconfig - scope: Namespaced - versions: - - name: v2 - schema: - openAPIV3Schema: - description: ApisixPluginConfig defines a reusable set of plugin configuration - that can be referenced by routes. - 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: ApisixPluginConfigSpec defines the plugin config configuration. - properties: - ingressClassName: - description: |- - IngressClassName is the name of an IngressClass cluster resource. - The controller uses this field to decide whether the resource should be managed. - type: string - plugins: - description: Plugins contain a list of plugins. - items: - description: ApisixRoutePlugin represents an APISIX plugin. - properties: - config: - description: Plugin configuration. - x-kubernetes-preserve-unknown-fields: true - enable: - default: true - description: Whether this plugin is in use, default is true. - type: boolean - name: - description: The plugin name. - type: string - secretRef: - description: Plugin configuration secretRef. - type: string - required: - - enable - - name - type: object - type: array - required: - - plugins - type: object - status: - description: ApisixStatus is the status report for Apisix ingress Resources - properties: - conditions: - 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 - type: array - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixroutes.yaml b/config/crd-nocel/apisix.apache.org_apisixroutes.yaml deleted file mode 100644 index 899937eae..000000000 --- a/config/crd-nocel/apisix.apache.org_apisixroutes.yaml +++ /dev/null @@ -1,519 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.2 - name: apisixroutes.apisix.apache.org -spec: - group: apisix.apache.org - names: - kind: ApisixRoute - listKind: ApisixRouteList - plural: apisixroutes - shortNames: - - ar - singular: apisixroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: HTTP Hosts - jsonPath: .spec.http[].match.hosts - name: Hosts - type: string - - description: HTTP Paths - jsonPath: .spec.http[].match.paths - name: URIs - type: string - - description: Backend Service for HTTP - jsonPath: .spec.http[].backends[].serviceName - name: Target Service (HTTP) - priority: 1 - type: string - - description: TCP Ingress Port - jsonPath: .spec.tcp[].match.ingressPort - name: Ingress Port (TCP) - priority: 1 - type: integer - - description: Backend Service for TCP - jsonPath: .spec.tcp[].match.backend.serviceName - name: Target Service (TCP) - priority: 1 - type: string - - description: Creation time - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2 - schema: - openAPIV3Schema: - description: ApisixRoute is defines configuration for HTTP and stream routes. - 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: ApisixRouteSpec defines HTTP and stream route configuration. - properties: - http: - description: |- - HTTP defines a list of HTTP route rules. - Each rule specifies conditions to match HTTP requests and how to forward them. - items: - description: ApisixRouteHTTP represents a single HTTP route configuration. - properties: - authentication: - description: Authentication holds authentication-related configuration - for this route. - properties: - enable: - description: Enable toggles authentication on or off. - type: boolean - jwtAuth: - description: JwtAuth defines configuration for JWT authentication. - properties: - cookie: - description: Cookie specifies the cookie name to look - for the JWT token. - type: string - header: - description: Header specifies the HTTP header name to - look for the JWT token. - type: string - query: - description: Query specifies the URL query parameter - name to look for the JWT token. - type: string - type: object - keyAuth: - description: KeyAuth defines configuration for key authentication. - properties: - header: - description: Header specifies the HTTP header name to - look for the key authentication token. - type: string - type: object - ldapAuth: - description: LDAPAuth defines configuration for LDAP authentication. - properties: - base_dn: - description: BaseDN is the base distinguished name (DN) - for LDAP searches. - type: string - ldap_uri: - description: LDAPURI is the URI of the LDAP server. - type: string - uid: - description: UID is the user identifier attribute in - LDAP. - type: string - use_tls: - description: UseTLS indicates whether to use TLS for - the LDAP connection. - type: boolean - type: object - type: - description: Type specifies the authentication type. - type: string - required: - - enable - - type - type: object - backends: - description: |- - Backends lists potential backend services to proxy requests to. - If more than one backend is specified, the `traffic-split` plugin is used - to distribute traffic according to backend weights. - items: - description: ApisixRouteHTTPBackend represents an HTTP backend - (Kubernetes Service). - properties: - resolveGranularity: - description: |- - ResolveGranularity determines how the backend service is resolved. - Valid values are `endpoints` and `service`. When set to `endpoints`, - individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. - The default is `endpoints`. - type: string - serviceName: - description: |- - ServiceName is the name of the Kubernetes Service. - Cross-namespace references are not supported—ensure the ApisixRoute - and the Service are in the same namespace. - type: string - servicePort: - anyOf: - - type: integer - - type: string - description: |- - ServicePort is the port of the Kubernetes Service. - This can be either the port name or port number. - x-kubernetes-int-or-string: true - subset: - description: |- - Subset specifies a named subset of the target Service. - The subset must be pre-defined in the corresponding ApisixUpstream resource. - type: string - weight: - description: Weight specifies the relative traffic weight - for this backend. - type: integer - required: - - serviceName - - servicePort - type: object - type: array - match: - description: Match defines the HTTP request matching criteria. - properties: - exprs: - description: NginxVars defines match conditions based on - Nginx variables. - items: - description: ApisixRouteHTTPMatchExpr represents a binary - expression used to match requests based on Nginx variables. - properties: - op: - description: |- - Op specifies the operator used in the expression. - Can be `Equal`, `NotEqual`, `GreaterThan`, `GreaterThanEqual`, `LessThan`, `LessThanEqual`, `RegexMatch`, - `RegexNotMatch`, `RegexMatchCaseInsensitive`, `RegexNotMatchCaseInsensitive`, `In`, or `NotIn`. - type: string - set: - description: |- - Set provides a list of acceptable values for the expression. - This should be used when Op is `In` or `NotIn`. - items: - type: string - type: array - subject: - description: |- - Subject defines the left-hand side of the expression. - It can be any [built-in variable](/apisix/reference/built-in-variables) or string literal. - properties: - name: - description: Name is the name of the header or - query parameter. - type: string - scope: - description: |- - Scope specifies the subject scope and can be `Header`, `Query`, or `Path`. - When Scope is `Path`, Name will be ignored. - type: string - required: - - name - - scope - type: object - value: - description: |- - Value defines a single value to compare against the subject. - This should be used when Op is not `In` or `NotIn`. - Set and Value are mutually exclusive—only one should be set at a time. - type: string - required: - - op - - subject - type: object - type: array - filter_func: - description: |- - FilterFunc is a user-defined function for advanced request filtering. - The function can use Nginx variables through the `vars` parameter. - This field is supported in APISIX but not in API7 Enterprise. - type: string - hosts: - description: |- - Hosts specifies Host header values to match. - Supports exact and wildcard domains. - Only one level of wildcard is allowed (e.g., `*.example.com` is valid, - but `*.*.example.com` is not). - items: - type: string - type: array - methods: - description: Methods specifies the HTTP methods to match. - items: - type: string - type: array - paths: - description: |- - Paths is a list of URI path patterns to match. - At least one path must be specified. - Supports exact matches and prefix matches. - For prefix matches, append `*` to the path, such as `/foo*`. - items: - type: string - type: array - remoteAddrs: - description: |- - RemoteAddrs is a list of source IP addresses or CIDR ranges to match. - Supports both IPv4 and IPv6 formats. - items: - type: string - type: array - required: - - paths - type: object - name: - description: Name is the unique rule name and cannot be empty. - type: string - plugin_config_name: - description: PluginConfigName specifies the name of the plugin - config to apply. - type: string - plugin_config_namespace: - description: |- - PluginConfigNamespace specifies the namespace of the plugin config. - Defaults to the namespace of the ApisixRoute if not set. - type: string - plugins: - description: Plugins lists additional plugins applied to this - route. - items: - description: ApisixRoutePlugin represents an APISIX plugin. - properties: - config: - description: Plugin configuration. - x-kubernetes-preserve-unknown-fields: true - enable: - default: true - description: Whether this plugin is in use, default is - true. - type: boolean - name: - description: The plugin name. - type: string - secretRef: - description: Plugin configuration secretRef. - type: string - required: - - enable - - name - type: object - type: array - priority: - description: |- - Priority defines the route priority when multiple routes share the same URI path. - Higher values mean higher priority in route matching. - type: integer - timeout: - description: Timeout specifies upstream timeout settings. - properties: - connect: - description: Connect timeout for establishing a connection - to the upstream. - type: string - read: - description: Read timeout for reading data from the upstream. - type: string - send: - description: Send timeout for sending data to the upstream. - type: string - type: object - upstreams: - description: Upstreams references ApisixUpstream CRDs. - items: - description: |- - ApisixRouteUpstreamReference references an ApisixUpstream CRD to be used as a backend. - It can be used in traffic-splitting scenarios or to select a specific upstream configuration. - properties: - name: - description: Name is the name of the ApisixUpstream resource. - type: string - weight: - description: Weight is the weight assigned to this upstream. - type: integer - type: object - type: array - websocket: - description: Websocket enables or disables websocket support - for this route. - type: boolean - required: - - name - type: object - type: array - ingressClassName: - description: |- - IngressClassName is the name of the IngressClass this route belongs to. - It allows multiple controllers to watch and reconcile different routes. - type: string - stream: - description: |- - Stream defines a list of stream route rules. - Each rule specifies conditions to match TCP/UDP traffic and how to forward them. - items: - description: ApisixRouteStream defines the configuration for a Layer - 4 (TCP/UDP) route. - properties: - backend: - description: Backend specifies the destination service to which - traffic should be forwarded. - properties: - resolveGranularity: - description: |- - ResolveGranularity determines how the backend service is resolved. - Valid values are `endpoints` and `service`. When set to `endpoints`, - individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. - The default is `endpoints`. - type: string - serviceName: - description: |- - ServiceName is the name of the Kubernetes Service. - Cross-namespace references are not supported—ensure the ApisixRoute - and the Service are in the same namespace. - type: string - servicePort: - anyOf: - - type: integer - - type: string - description: |- - ServicePort is the port of the Kubernetes Service. - This can be either the port name or port number. - x-kubernetes-int-or-string: true - subset: - description: |- - Subset specifies a named subset of the target Service. - The subset must be pre-defined in the corresponding ApisixUpstream resource. - type: string - required: - - serviceName - - servicePort - type: object - match: - description: Match defines the criteria used to match incoming - TCP or UDP connections. - properties: - host: - description: Host is the destination host address used to - match the incoming TCP/UDP traffic. - type: string - ingressPort: - description: |- - IngressPort is the port on which the APISIX Ingress proxy server listens. - This must be a statically configured port, as APISIX does not support dynamic port binding. - format: int32 - type: integer - required: - - ingressPort - type: object - name: - description: Name is a unique identifier for the route. This - field must not be empty. - type: string - plugins: - description: Plugins defines a list of plugins to apply to this - route. - items: - description: ApisixRoutePlugin represents an APISIX plugin. - properties: - config: - description: Plugin configuration. - x-kubernetes-preserve-unknown-fields: true - enable: - default: true - description: Whether this plugin is in use, default is - true. - type: boolean - name: - description: The plugin name. - type: string - secretRef: - description: Plugin configuration secretRef. - type: string - required: - - enable - - name - type: object - type: array - protocol: - description: Protocol specifies the L4 protocol to match. Can - be `tcp` or `udp`. - type: string - required: - - backend - - match - - name - - protocol - type: object - type: array - type: object - status: - description: ApisixStatus is the status report for Apisix ingress Resources - properties: - conditions: - 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 - type: array - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixtlses.yaml b/config/crd-nocel/apisix.apache.org_apisixtlses.yaml deleted file mode 100644 index 65f06eeb7..000000000 --- a/config/crd-nocel/apisix.apache.org_apisixtlses.yaml +++ /dev/null @@ -1,195 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.2 - name: apisixtlses.apisix.apache.org -spec: - group: apisix.apache.org - names: - kind: ApisixTls - listKind: ApisixTlsList - plural: apisixtlses - shortNames: - - atls - singular: apisixtls - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.hosts - name: SNIs - type: string - - jsonPath: .spec.secret.name - name: Secret Name - type: string - - jsonPath: .spec.secret.namespace - name: Secret Namespace - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.client.ca.name - name: Client CA Secret Name - type: string - - jsonPath: .spec.client.ca.namespace - name: Client CA Secret Namespace - type: string - name: v2 - schema: - openAPIV3Schema: - description: ApisixTls defines configuration for TLS and mutual TLS (mTLS). - 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: ApisixTlsSpec defines the TLS configuration. - properties: - client: - description: Client defines mutual TLS (mTLS) settings, such as the - CA certificate and verification depth. - properties: - caSecret: - description: CASecret references the secret containing the CA - certificate for client certificate validation. - properties: - name: - description: Name is the name of the Kubernetes Secret. - minLength: 1 - type: string - namespace: - description: Namespace is the namespace where the Kubernetes - Secret is located. - minLength: 1 - type: string - required: - - name - - namespace - type: object - depth: - description: Depth specifies the maximum verification depth for - the client certificate chain. - type: integer - skip_mtls_uri_regex: - description: SkipMTLSUriRegex contains RegEx patterns for URIs - to skip mutual TLS verification. - items: - type: string - type: array - type: object - hosts: - description: |- - Hosts lists the SNI (Server Name Indication) hostnames that this TLS configuration applies to. - Must contain at least one host. - items: - pattern: ^\*?[0-9a-zA-Z-.]+$ - type: string - minItems: 1 - type: array - ingressClassName: - description: |- - IngressClassName specifies which IngressClass this resource is associated with. - The APISIX controller only processes this resource if the class matches its own. - type: string - secret: - description: |- - Secret refers to the Kubernetes TLS secret containing the certificate and private key. - This secret must exist in the specified namespace and contain valid TLS data. - properties: - name: - description: Name is the name of the Kubernetes Secret. - minLength: 1 - type: string - namespace: - description: Namespace is the namespace where the Kubernetes Secret - is located. - minLength: 1 - type: string - required: - - name - - namespace - type: object - required: - - hosts - - secret - type: object - status: - description: ApisixStatus is the status report for Apisix ingress Resources - properties: - conditions: - 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 - type: array - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd-nocel/apisix.apache.org_apisixupstreams.yaml b/config/crd-nocel/apisix.apache.org_apisixupstreams.yaml deleted file mode 100644 index 5d770b71a..000000000 --- a/config/crd-nocel/apisix.apache.org_apisixupstreams.yaml +++ /dev/null @@ -1,781 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.2 - name: apisixupstreams.apisix.apache.org -spec: - group: apisix.apache.org - names: - kind: ApisixUpstream - listKind: ApisixUpstreamList - plural: apisixupstreams - shortNames: - - au - singular: apisixupstream - scope: Namespaced - versions: - - name: v2 - schema: - openAPIV3Schema: - description: ApisixUpstream defines configuration for upstream services. - 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: ApisixUpstreamSpec defines the upstream configuration. - properties: - discovery: - description: |- - Discovery configures service discovery for the upstream. - Deprecated: no longer supported in standalone mode. - properties: - args: - additionalProperties: - type: string - description: |- - Args contains additional configuration parameters required by the discovery provider. - These are passed as key-value pairs. - type: object - serviceName: - description: ServiceName is the name of the service to discover. - type: string - type: - description: Type is the name of the service discovery provider. - type: string - required: - - serviceName - - type - type: object - externalNodes: - description: |- - ExternalNodes defines a static list of backend nodes located outside the cluster. - When this field is set, the upstream will route traffic directly to these nodes - without DNS resolution or service discovery. - items: - description: |- - ApisixUpstreamExternalNode defines configuration for an external upstream node. - This allows referencing services outside the cluster. - properties: - name: - description: Name is the hostname or IP address of the external - node. - type: string - port: - description: Port specifies the port number on which the external - node is accepting traffic. - type: integer - type: - description: Type indicates the kind of external node. Can be - `Domain`, or `Service`. - type: string - weight: - description: |- - Weight defines the load balancing weight of this node. - Higher values increase the share of traffic sent to this node. - type: integer - type: object - minItems: 1 - type: array - healthCheck: - description: |- - HealthCheck defines the active and passive health check configuration for the upstream. - Deprecated: no longer supported in standalone mode. - properties: - active: - description: Active health checks proactively send requests to - upstream nodes to determine their availability. - properties: - concurrency: - description: Concurrency sets the number of targets to be - checked at the same time. - minimum: 0 - type: integer - healthy: - description: Healthy configures the rules that define an upstream - node as healthy. - properties: - httpCodes: - description: HTTPCodes define a list of HTTP status codes - that are considered healthy. - items: - type: integer - minItems: 1 - type: array - interval: - description: Interval defines the time interval for checking - targets, in seconds. - type: string - successes: - description: Successes define the number of successful - probes to define a healthy target. - maximum: 254 - minimum: 0 - type: integer - type: object - host: - description: Host sets the upstream host. - type: string - httpPath: - description: HTTPPath sets the HTTP probe request path. - type: string - port: - description: Port sets the upstream port. - format: int32 - maximum: 65535 - minimum: 0 - type: integer - requestHeaders: - description: RequestHeaders sets the request headers. - items: - type: string - type: array - strictTLS: - description: StrictTLS sets whether to enforce TLS. - type: boolean - timeout: - description: Timeout sets health check timeout in seconds. - format: int64 - type: integer - type: - description: Type is the health check type. Can be `http`, - `https`, or `tcp`. - enum: - - http - - https - - tcp - type: string - unhealthy: - description: Unhealthy configures the rules that define an - upstream node as unhealthy. - properties: - httpCodes: - description: HTTPCodes define a list of HTTP status codes - that are considered unhealthy. - items: - type: integer - minItems: 1 - type: array - httpFailures: - description: HTTPFailures define the number of HTTP failures - to define an unhealthy target. - maximum: 254 - minimum: 0 - type: integer - interval: - description: Interval defines the time interval for checking - targets, in seconds. - type: string - tcpFailures: - description: TCPFailures define the number of TCP failures - to define an unhealthy target. - maximum: 254 - minimum: 0 - type: integer - timeout: - description: Timeout sets health check timeout in seconds. - type: integer - type: object - type: object - passive: - description: Passive health checks evaluate upstream health based - on observed traffic, such as timeouts or errors. - properties: - healthy: - description: Healthy defines the conditions under which an - upstream node is considered healthy. - properties: - httpCodes: - description: HTTPCodes define a list of HTTP status codes - that are considered healthy. - items: - type: integer - minItems: 1 - type: array - successes: - description: Successes define the number of successful - probes to define a healthy target. - maximum: 254 - minimum: 0 - type: integer - type: object - type: - description: |- - Type specifies the type of passive health check. - Can be `http`, `https`, or `tcp`. - type: string - unhealthy: - description: Unhealthy defines the conditions under which - an upstream node is considered unhealthy. - properties: - httpCodes: - description: HTTPCodes define a list of HTTP status codes - that are considered unhealthy. - items: - type: integer - minItems: 1 - type: array - httpFailures: - description: HTTPFailures define the number of HTTP failures - to define an unhealthy target. - maximum: 254 - minimum: 0 - type: integer - tcpFailures: - description: TCPFailures define the number of TCP failures - to define an unhealthy target. - maximum: 254 - minimum: 0 - type: integer - timeout: - description: Timeout sets health check timeout in seconds. - type: integer - type: object - type: object - required: - - active - type: object - ingressClassName: - description: |- - IngressClassName is the name of an IngressClass cluster resource. - Controller implementations use this field to determine whether they - should process this ApisixUpstream resource. - type: string - loadbalancer: - description: LoadBalancer specifies the load balancer configuration - for Kubernetes Service. - properties: - hashOn: - default: vars - description: |- - HashOn specified the type of field used for hashing, required when type is `chash`. - Default is `vars`. Can be `vars`, `header`, `cookie`, `consumer`, or `vars_combinations`. - enum: - - vars - - header - - cookie - - consumer - - vars_combinations - type: string - key: - description: |- - Key is used with HashOn, generally required when type is `chash`. - When HashOn is `header` or `cookie`, specifies the name of the header or cookie. - When HashOn is `consumer`, key is not required, as the consumer name is used automatically. - When HashOn is `vars` or `vars_combinations`, key refers to one or a combination of - [built-in variables](/enterprise/reference/built-in-variables). - type: string - type: - default: roundrobin - description: |- - Type specifies the load balancing algorithms to route traffic to the backend. - Default is `roundrobin`. - Can be `roundrobin`, `chash`, `ewma`, or `least_conn`. - enum: - - roundrobin - - chash - - ewma - - least_conn - type: string - required: - - type - type: object - passHost: - description: |- - PassHost configures how the host header should be determined when a - request is forwarded to the upstream. - Default is `pass`. - Can be `pass`, `node` or `rewrite`: - * `pass`: preserve the original Host header - * `node`: use the upstream node’s host - * `rewrite`: set to a custom host via upstreamHost - enum: - - pass - - node - - rewrite - type: string - portLevelSettings: - description: |- - PortLevelSettings allows fine-grained upstream configuration for specific ports, - useful when a backend service exposes multiple ports with different behaviors or protocols. - items: - description: |- - PortLevelSettings configures the ApisixUpstreamConfig for each individual port. It inherits - configuration from the outer level (the whole Kubernetes Service) and overrides some of - them if they are set on the port level. - properties: - discovery: - description: |- - Discovery configures service discovery for the upstream. - Deprecated: no longer supported in standalone mode. - properties: - args: - additionalProperties: - type: string - description: |- - Args contains additional configuration parameters required by the discovery provider. - These are passed as key-value pairs. - type: object - serviceName: - description: ServiceName is the name of the service to discover. - type: string - type: - description: Type is the name of the service discovery provider. - type: string - required: - - serviceName - - type - type: object - healthCheck: - description: |- - HealthCheck defines the active and passive health check configuration for the upstream. - Deprecated: no longer supported in standalone mode. - properties: - active: - description: Active health checks proactively send requests - to upstream nodes to determine their availability. - properties: - concurrency: - description: Concurrency sets the number of targets - to be checked at the same time. - minimum: 0 - type: integer - healthy: - description: Healthy configures the rules that define - an upstream node as healthy. - properties: - httpCodes: - description: HTTPCodes define a list of HTTP status - codes that are considered healthy. - items: - type: integer - minItems: 1 - type: array - interval: - description: Interval defines the time interval - for checking targets, in seconds. - type: string - successes: - description: Successes define the number of successful - probes to define a healthy target. - maximum: 254 - minimum: 0 - type: integer - type: object - host: - description: Host sets the upstream host. - type: string - httpPath: - description: HTTPPath sets the HTTP probe request path. - type: string - port: - description: Port sets the upstream port. - format: int32 - maximum: 65535 - minimum: 0 - type: integer - requestHeaders: - description: RequestHeaders sets the request headers. - items: - type: string - type: array - strictTLS: - description: StrictTLS sets whether to enforce TLS. - type: boolean - timeout: - description: Timeout sets health check timeout in seconds. - format: int64 - type: integer - type: - description: Type is the health check type. Can be `http`, - `https`, or `tcp`. - enum: - - http - - https - - tcp - type: string - unhealthy: - description: Unhealthy configures the rules that define - an upstream node as unhealthy. - properties: - httpCodes: - description: HTTPCodes define a list of HTTP status - codes that are considered unhealthy. - items: - type: integer - minItems: 1 - type: array - httpFailures: - description: HTTPFailures define the number of HTTP - failures to define an unhealthy target. - maximum: 254 - minimum: 0 - type: integer - interval: - description: Interval defines the time interval - for checking targets, in seconds. - type: string - tcpFailures: - description: TCPFailures define the number of TCP - failures to define an unhealthy target. - maximum: 254 - minimum: 0 - type: integer - timeout: - description: Timeout sets health check timeout in - seconds. - type: integer - type: object - type: object - passive: - description: Passive health checks evaluate upstream health - based on observed traffic, such as timeouts or errors. - properties: - healthy: - description: Healthy defines the conditions under which - an upstream node is considered healthy. - properties: - httpCodes: - description: HTTPCodes define a list of HTTP status - codes that are considered healthy. - items: - type: integer - minItems: 1 - type: array - successes: - description: Successes define the number of successful - probes to define a healthy target. - maximum: 254 - minimum: 0 - type: integer - type: object - type: - description: |- - Type specifies the type of passive health check. - Can be `http`, `https`, or `tcp`. - type: string - unhealthy: - description: Unhealthy defines the conditions under - which an upstream node is considered unhealthy. - properties: - httpCodes: - description: HTTPCodes define a list of HTTP status - codes that are considered unhealthy. - items: - type: integer - minItems: 1 - type: array - httpFailures: - description: HTTPFailures define the number of HTTP - failures to define an unhealthy target. - maximum: 254 - minimum: 0 - type: integer - tcpFailures: - description: TCPFailures define the number of TCP - failures to define an unhealthy target. - maximum: 254 - minimum: 0 - type: integer - timeout: - description: Timeout sets health check timeout in - seconds. - type: integer - type: object - type: object - required: - - active - type: object - loadbalancer: - description: LoadBalancer specifies the load balancer configuration - for Kubernetes Service. - properties: - hashOn: - default: vars - description: |- - HashOn specified the type of field used for hashing, required when type is `chash`. - Default is `vars`. Can be `vars`, `header`, `cookie`, `consumer`, or `vars_combinations`. - enum: - - vars - - header - - cookie - - consumer - - vars_combinations - type: string - key: - description: |- - Key is used with HashOn, generally required when type is `chash`. - When HashOn is `header` or `cookie`, specifies the name of the header or cookie. - When HashOn is `consumer`, key is not required, as the consumer name is used automatically. - When HashOn is `vars` or `vars_combinations`, key refers to one or a combination of - [built-in variables](/enterprise/reference/built-in-variables). - type: string - type: - default: roundrobin - description: |- - Type specifies the load balancing algorithms to route traffic to the backend. - Default is `roundrobin`. - Can be `roundrobin`, `chash`, `ewma`, or `least_conn`. - enum: - - roundrobin - - chash - - ewma - - least_conn - type: string - required: - - type - type: object - passHost: - description: |- - PassHost configures how the host header should be determined when a - request is forwarded to the upstream. - Default is `pass`. - Can be `pass`, `node` or `rewrite`: - * `pass`: preserve the original Host header - * `node`: use the upstream node’s host - * `rewrite`: set to a custom host via upstreamHost - enum: - - pass - - node - - rewrite - type: string - port: - description: Port is a Kubernetes Service port. - format: int32 - type: integer - retries: - description: |- - Retries defines the number of retry attempts APISIX should make when a failure occurs. - Failures include timeouts, network errors, or 5xx status codes. - format: int64 - type: integer - scheme: - description: |- - Scheme is the protocol used to communicate with the upstream. - Default is `http`. - Can be `http`, `https`, `grpc`, or `grpcs`. - enum: - - http - - https - - grpc - - grpcs - type: string - subsets: - description: |- - Subsets defines labeled subsets of service endpoints, typically used for - service versioning or canary deployments. - items: - description: ApisixUpstreamSubset defines a single endpoints - group of one Service. - properties: - labels: - additionalProperties: - type: string - description: Labels is the label set of this subset. - type: object - name: - description: Name is the name of subset. - type: string - required: - - labels - - name - type: object - type: array - timeout: - description: Timeout specifies the connection, send, and read - timeouts for upstream requests. - properties: - connect: - description: Connect timeout for establishing a connection - to the upstream. - type: string - read: - description: Read timeout for reading data from the upstream. - type: string - send: - description: Send timeout for sending data to the upstream. - type: string - type: object - tlsSecret: - description: |- - TLSSecret references a Kubernetes Secret that contains the client certificate and key - for mutual TLS when connecting to the upstream. - properties: - name: - description: Name is the name of the Kubernetes Secret. - minLength: 1 - type: string - namespace: - description: Namespace is the namespace where the Kubernetes - Secret is located. - minLength: 1 - type: string - required: - - name - - namespace - type: object - upstreamHost: - description: UpstreamHost sets a custom Host header when passHost - is set to `rewrite`. - type: string - required: - - port - type: object - type: array - retries: - description: |- - Retries defines the number of retry attempts APISIX should make when a failure occurs. - Failures include timeouts, network errors, or 5xx status codes. - format: int64 - type: integer - scheme: - description: |- - Scheme is the protocol used to communicate with the upstream. - Default is `http`. - Can be `http`, `https`, `grpc`, or `grpcs`. - enum: - - http - - https - - grpc - - grpcs - type: string - subsets: - description: |- - Subsets defines labeled subsets of service endpoints, typically used for - service versioning or canary deployments. - items: - description: ApisixUpstreamSubset defines a single endpoints group - of one Service. - properties: - labels: - additionalProperties: - type: string - description: Labels is the label set of this subset. - type: object - name: - description: Name is the name of subset. - type: string - required: - - labels - - name - type: object - type: array - timeout: - description: Timeout specifies the connection, send, and read timeouts - for upstream requests. - properties: - connect: - description: Connect timeout for establishing a connection to - the upstream. - type: string - read: - description: Read timeout for reading data from the upstream. - type: string - send: - description: Send timeout for sending data to the upstream. - type: string - type: object - tlsSecret: - description: |- - TLSSecret references a Kubernetes Secret that contains the client certificate and key - for mutual TLS when connecting to the upstream. - properties: - name: - description: Name is the name of the Kubernetes Secret. - minLength: 1 - type: string - namespace: - description: Namespace is the namespace where the Kubernetes Secret - is located. - minLength: 1 - type: string - required: - - name - - namespace - type: object - upstreamHost: - description: UpstreamHost sets a custom Host header when passHost - is set to `rewrite`. - type: string - type: object - status: - description: ApisixStatus is the status report for Apisix ingress Resources - properties: - conditions: - 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 - type: array - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd-nocel/apisix.apache.org_gatewayproxies.yaml b/config/crd-nocel/apisix.apache.org_gatewayproxies.yaml deleted file mode 100644 index 1cc471fb1..000000000 --- a/config/crd-nocel/apisix.apache.org_gatewayproxies.yaml +++ /dev/null @@ -1,168 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.2 - name: gatewayproxies.apisix.apache.org -spec: - group: apisix.apache.org - names: - kind: GatewayProxy - listKind: GatewayProxyList - plural: gatewayproxies - singular: gatewayproxy - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: GatewayProxy defines configuration for the gateway proxy instances - used to route traffic to services. - 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: |- - GatewayProxySpec defines configuration of gateway proxy instances, - including networking settings, global plugins, and plugin metadata. - properties: - pluginMetadata: - additionalProperties: - x-kubernetes-preserve-unknown-fields: true - description: PluginMetadata configures common configuration shared - by all plugin instances of the same name. - type: object - plugins: - description: Plugins configure global plugins. - items: - description: GatewayProxyPlugin contains plugin configuration. - properties: - config: - description: Config defines the plugin's configuration details. - x-kubernetes-preserve-unknown-fields: true - enabled: - description: Enabled defines whether the plugin is enabled. - type: boolean - name: - description: Name is the name of the plugin. - type: string - type: object - type: array - provider: - description: Provider configures the provider details. - properties: - controlPlane: - description: ControlPlane specifies the configuration for control - plane provider. - properties: - auth: - description: Auth specifies the authentication configuration. - properties: - adminKey: - description: AdminKey specifies the admin key authentication - configuration. - properties: - value: - description: Value sets the admin key value explicitly - (not recommended for production). - type: string - valueFrom: - description: ValueFrom specifies the source of the - admin key. - properties: - secretKeyRef: - description: SecretKeyRef references a key in - a Secret. - properties: - key: - description: Key is the key in the secret - to retrieve the secret from. - type: string - name: - description: Name is the name of the secret. - type: string - required: - - key - - name - type: object - type: object - type: object - type: - description: |- - Type specifies the type of authentication. - Can only be `AdminKey`. - enum: - - AdminKey - type: string - required: - - type - type: object - endpoints: - description: Endpoints specifies the list of control plane - endpoints. - items: - type: string - minItems: 1 - type: array - service: - properties: - name: - description: Name is the name of the provider. - type: string - port: - description: Port is the port of the provider. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - tlsVerify: - description: TlsVerify specifies whether to verify the TLS - certificate of the control plane. - type: boolean - required: - - auth - type: object - type: - description: Type specifies the type of provider. Can only be - `ControlPlane`. - enum: - - ControlPlane - type: string - required: - - type - type: object - publishService: - description: |- - PublishService specifies the LoadBalancer-type Service whose external address the controller uses to - update the status of Ingress resources. - type: string - statusAddress: - description: |- - StatusAddress specifies the external IP addresses that the controller uses to populate the status field - of GatewayProxy or Ingress resources for developers to access. - items: - type: string - type: array - type: object - type: object - served: true - storage: true diff --git a/config/crd-nocel/apisix.apache.org_v2.yaml b/config/crd-nocel/apisix.apache.org_v2.yaml new file mode 100644 index 000000000..3d71c9577 --- /dev/null +++ b/config/crd-nocel/apisix.apache.org_v2.yaml @@ -0,0 +1,2317 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixconsumers.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixConsumer + listKind: ApisixConsumerList + plural: apisixconsumers + shortNames: + - ac + singular: apisixconsumer + scope: Namespaced + versions: + - name: v2 + schema: + openAPIV3Schema: + description: ApisixConsumer defines configuration of a consumer and their + authentication details. + 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: ApisixConsumerSpec defines the consumer authentication configuration. + properties: + authParameter: + description: AuthParameter defines the authentication credentials + and configuration for this consumer. + properties: + basicAuth: + description: BasicAuth configures the basic authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the basic authentication credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies the basic authentication credentials. + properties: + password: + description: Password is the basic authentication password. + type: string + username: + description: Username is the basic authentication username. + type: string + required: + - password + - username + type: object + type: object + hmacAuth: + description: HMACAuth configures the HMAC authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the HMAC credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies HMAC authentication credentials. + properties: + access_key: + description: AccessKey is the identifier used to look + up the HMAC secret. + type: string + algorithm: + description: Algorithm specifies the hashing algorithm + (e.g., "hmac-sha256"). + type: string + clock_skew: + description: ClockSkew is the allowed time difference + (in seconds) between client and server clocks. + format: int64 + type: integer + encode_uri_params: + description: EncodeURIParams indicates whether URI parameters + are encoded when calculating the signature. + type: boolean + keep_headers: + description: KeepHeaders determines whether the HMAC signature + headers are preserved after verification. + type: boolean + max_req_body: + description: MaxReqBody sets the maximum size (in bytes) + of the request body that can be validated. + format: int64 + type: integer + secret_key: + description: SecretKey is the HMAC secret used to sign + the request. + type: string + signed_headers: + description: SignedHeaders lists the headers that must + be included in the signature. + items: + type: string + type: array + validate_request_body: + description: ValidateRequestBody enables HMAC validation + of the request body. + type: boolean + required: + - access_key + - secret_key + type: object + type: object + jwtAuth: + description: JwtAuth configures the JWT authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + JWT authentication credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies JWT authentication credentials. + properties: + algorithm: + description: |- + Algorithm specifies the signing algorithm. + Can be `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`, or `EdDSA`. + Currently APISIX only supports `HS256`, `HS512`, `RS256`, and `ES256`. API7 Enterprise supports all algorithms. + type: string + base64_secret: + description: Base64Secret indicates whether the secret + is base64-encoded. + type: boolean + exp: + description: Exp is the token expiration period in seconds. + format: int64 + type: integer + key: + description: Key is the unique identifier for the JWT + credential. + type: string + lifetime_grace_period: + description: LifetimeGracePeriod is the allowed clock + skew in seconds for token expiration. + format: int64 + type: integer + private_key: + description: PrivateKey is the private key used to sign + the JWT (for asymmetric algorithms). + type: string + public_key: + description: PublicKey is the public key used to verify + JWT signatures (for asymmetric algorithms). + type: string + secret: + description: Secret is the shared secret used to sign + the JWT (for symmetric algorithms). + type: string + required: + - key + - private_key + type: object + type: object + keyAuth: + description: KeyAuth configures the key authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the key authentication credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies the key authentication credentials. + properties: + key: + description: Key is the credential used for key authentication. + type: string + required: + - key + type: object + type: object + ldapAuth: + description: LDAPAuth configures the LDAP authentication details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the LDAP credentials. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies LDAP authentication credentials. + properties: + user_dn: + description: UserDN is the distinguished name (DN) of + the LDAP user. + type: string + required: + - user_dn + type: object + required: + - secretRef + type: object + wolfRBAC: + description: WolfRBAC configures the Wolf RBAC authentication + details. + properties: + secretRef: + description: SecretRef references a Kubernetes Secret containing + the Wolf RBAC token. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + value: + description: Value specifies the Wolf RBAC token. + properties: + appid: + description: Appid is the application identifier used + when communicating with the Wolf RBAC server. + type: string + header_prefix: + description: HeaderPrefix is the prefix added to request + headers for RBAC enforcement. + type: string + server: + description: Server is the URL of the Wolf RBAC server. + type: string + type: object + type: object + type: object + ingressClassName: + description: |- + IngressClassName is the name of an IngressClass cluster resource. + The controller uses this field to decide whether the resource should be managed. + type: string + required: + - authParameter + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixglobalrules.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixGlobalRule + listKind: ApisixGlobalRuleList + plural: apisixglobalrules + shortNames: + - agr + singular: apisixglobalrule + scope: Namespaced + versions: + - name: v2 + schema: + openAPIV3Schema: + description: ApisixGlobalRule defines configuration for global plugins. + 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: ApisixGlobalRuleSpec defines the global plugin configuration. + properties: + ingressClassName: + description: |- + IngressClassName is the name of an IngressClass cluster resource. + The controller uses this field to decide whether the resource should be managed. + type: string + plugins: + description: Plugins contain a list of global plugins. + items: + description: ApisixRoutePlugin represents an APISIX plugin. + properties: + config: + description: Plugin configuration. + x-kubernetes-preserve-unknown-fields: true + enable: + default: true + description: Whether this plugin is in use, default is true. + type: boolean + name: + description: The plugin name. + type: string + secretRef: + description: Plugin configuration secretRef. + type: string + required: + - enable + - name + type: object + type: array + required: + - plugins + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixpluginconfigs.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixPluginConfig + listKind: ApisixPluginConfigList + plural: apisixpluginconfigs + shortNames: + - apc + singular: apisixpluginconfig + scope: Namespaced + versions: + - name: v2 + schema: + openAPIV3Schema: + description: ApisixPluginConfig defines a reusable set of plugin configuration + that can be referenced by routes. + 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: ApisixPluginConfigSpec defines the plugin config configuration. + properties: + ingressClassName: + description: |- + IngressClassName is the name of an IngressClass cluster resource. + The controller uses this field to decide whether the resource should be managed. + type: string + plugins: + description: Plugins contain a list of plugins. + items: + description: ApisixRoutePlugin represents an APISIX plugin. + properties: + config: + description: Plugin configuration. + x-kubernetes-preserve-unknown-fields: true + enable: + default: true + description: Whether this plugin is in use, default is true. + type: boolean + name: + description: The plugin name. + type: string + secretRef: + description: Plugin configuration secretRef. + type: string + required: + - enable + - name + type: object + type: array + required: + - plugins + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixroutes.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixRoute + listKind: ApisixRouteList + plural: apisixroutes + shortNames: + - ar + singular: apisixroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: HTTP Hosts + jsonPath: .spec.http[].match.hosts + name: Hosts + type: string + - description: HTTP Paths + jsonPath: .spec.http[].match.paths + name: URIs + type: string + - description: Backend Service for HTTP + jsonPath: .spec.http[].backends[].serviceName + name: Target Service (HTTP) + priority: 1 + type: string + - description: TCP Ingress Port + jsonPath: .spec.tcp[].match.ingressPort + name: Ingress Port (TCP) + priority: 1 + type: integer + - description: Backend Service for TCP + jsonPath: .spec.tcp[].match.backend.serviceName + name: Target Service (TCP) + priority: 1 + type: string + - description: Creation time + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2 + schema: + openAPIV3Schema: + description: ApisixRoute is defines configuration for HTTP and stream routes. + 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: ApisixRouteSpec defines HTTP and stream route configuration. + properties: + http: + description: |- + HTTP defines a list of HTTP route rules. + Each rule specifies conditions to match HTTP requests and how to forward them. + items: + description: ApisixRouteHTTP represents a single HTTP route configuration. + properties: + authentication: + description: Authentication holds authentication-related configuration + for this route. + properties: + enable: + description: Enable toggles authentication on or off. + type: boolean + jwtAuth: + description: JwtAuth defines configuration for JWT authentication. + properties: + cookie: + description: Cookie specifies the cookie name to look + for the JWT token. + type: string + header: + description: Header specifies the HTTP header name to + look for the JWT token. + type: string + query: + description: Query specifies the URL query parameter + name to look for the JWT token. + type: string + type: object + keyAuth: + description: KeyAuth defines configuration for key authentication. + properties: + header: + description: Header specifies the HTTP header name to + look for the key authentication token. + type: string + type: object + ldapAuth: + description: LDAPAuth defines configuration for LDAP authentication. + properties: + base_dn: + description: BaseDN is the base distinguished name (DN) + for LDAP searches. + type: string + ldap_uri: + description: LDAPURI is the URI of the LDAP server. + type: string + uid: + description: UID is the user identifier attribute in + LDAP. + type: string + use_tls: + description: UseTLS indicates whether to use TLS for + the LDAP connection. + type: boolean + type: object + type: + description: Type specifies the authentication type. + type: string + required: + - enable + - type + type: object + backends: + description: |- + Backends lists potential backend services to proxy requests to. + If more than one backend is specified, the `traffic-split` plugin is used + to distribute traffic according to backend weights. + items: + description: ApisixRouteHTTPBackend represents an HTTP backend + (Kubernetes Service). + properties: + resolveGranularity: + description: |- + ResolveGranularity determines how the backend service is resolved. + Valid values are `endpoints` and `service`. When set to `endpoints`, + individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. + The default is `endpoints`. + type: string + serviceName: + description: |- + ServiceName is the name of the Kubernetes Service. + Cross-namespace references are not supported—ensure the ApisixRoute + and the Service are in the same namespace. + type: string + servicePort: + anyOf: + - type: integer + - type: string + description: |- + ServicePort is the port of the Kubernetes Service. + This can be either the port name or port number. + x-kubernetes-int-or-string: true + subset: + description: |- + Subset specifies a named subset of the target Service. + The subset must be pre-defined in the corresponding ApisixUpstream resource. + type: string + weight: + description: Weight specifies the relative traffic weight + for this backend. + type: integer + required: + - serviceName + - servicePort + type: object + type: array + match: + description: Match defines the HTTP request matching criteria. + properties: + exprs: + description: NginxVars defines match conditions based on + Nginx variables. + items: + description: ApisixRouteHTTPMatchExpr represents a binary + expression used to match requests based on Nginx variables. + properties: + op: + description: |- + Op specifies the operator used in the expression. + Can be `Equal`, `NotEqual`, `GreaterThan`, `GreaterThanEqual`, `LessThan`, `LessThanEqual`, `RegexMatch`, + `RegexNotMatch`, `RegexMatchCaseInsensitive`, `RegexNotMatchCaseInsensitive`, `In`, or `NotIn`. + type: string + set: + description: |- + Set provides a list of acceptable values for the expression. + This should be used when Op is `In` or `NotIn`. + items: + type: string + type: array + subject: + description: |- + Subject defines the left-hand side of the expression. + It can be any [built-in variable](/apisix/reference/built-in-variables) or string literal. + properties: + name: + description: Name is the name of the header or + query parameter. + type: string + scope: + description: |- + Scope specifies the subject scope and can be `Header`, `Query`, or `Path`. + When Scope is `Path`, Name will be ignored. + type: string + required: + - name + - scope + type: object + value: + description: |- + Value defines a single value to compare against the subject. + This should be used when Op is not `In` or `NotIn`. + Set and Value are mutually exclusive—only one should be set at a time. + type: string + required: + - op + - subject + type: object + type: array + filter_func: + description: |- + FilterFunc is a user-defined function for advanced request filtering. + The function can use Nginx variables through the `vars` parameter. + This field is supported in APISIX but not in API7 Enterprise. + type: string + hosts: + description: |- + Hosts specifies Host header values to match. + Supports exact and wildcard domains. + Only one level of wildcard is allowed (e.g., `*.example.com` is valid, + but `*.*.example.com` is not). + items: + type: string + type: array + methods: + description: Methods specifies the HTTP methods to match. + items: + type: string + type: array + paths: + description: |- + Paths is a list of URI path patterns to match. + At least one path must be specified. + Supports exact matches and prefix matches. + For prefix matches, append `*` to the path, such as `/foo*`. + items: + type: string + type: array + remoteAddrs: + description: |- + RemoteAddrs is a list of source IP addresses or CIDR ranges to match. + Supports both IPv4 and IPv6 formats. + items: + type: string + type: array + required: + - paths + type: object + name: + description: Name is the unique rule name and cannot be empty. + type: string + plugin_config_name: + description: PluginConfigName specifies the name of the plugin + config to apply. + type: string + plugin_config_namespace: + description: |- + PluginConfigNamespace specifies the namespace of the plugin config. + Defaults to the namespace of the ApisixRoute if not set. + type: string + plugins: + description: Plugins lists additional plugins applied to this + route. + items: + description: ApisixRoutePlugin represents an APISIX plugin. + properties: + config: + description: Plugin configuration. + x-kubernetes-preserve-unknown-fields: true + enable: + default: true + description: Whether this plugin is in use, default is + true. + type: boolean + name: + description: The plugin name. + type: string + secretRef: + description: Plugin configuration secretRef. + type: string + required: + - enable + - name + type: object + type: array + priority: + description: |- + Priority defines the route priority when multiple routes share the same URI path. + Higher values mean higher priority in route matching. + type: integer + timeout: + description: Timeout specifies upstream timeout settings. + properties: + connect: + description: Connect timeout for establishing a connection + to the upstream. + type: string + read: + description: Read timeout for reading data from the upstream. + type: string + send: + description: Send timeout for sending data to the upstream. + type: string + type: object + upstreams: + description: Upstreams references ApisixUpstream CRDs. + items: + description: |- + ApisixRouteUpstreamReference references an ApisixUpstream CRD to be used as a backend. + It can be used in traffic-splitting scenarios or to select a specific upstream configuration. + properties: + name: + description: Name is the name of the ApisixUpstream resource. + type: string + weight: + description: Weight is the weight assigned to this upstream. + type: integer + type: object + type: array + websocket: + description: Websocket enables or disables websocket support + for this route. + type: boolean + required: + - name + type: object + type: array + ingressClassName: + description: |- + IngressClassName is the name of the IngressClass this route belongs to. + It allows multiple controllers to watch and reconcile different routes. + type: string + stream: + description: |- + Stream defines a list of stream route rules. + Each rule specifies conditions to match TCP/UDP traffic and how to forward them. + items: + description: ApisixRouteStream defines the configuration for a Layer + 4 (TCP/UDP) route. + properties: + backend: + description: Backend specifies the destination service to which + traffic should be forwarded. + properties: + resolveGranularity: + description: |- + ResolveGranularity determines how the backend service is resolved. + Valid values are `endpoints` and `service`. When set to `endpoints`, + individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. + The default is `endpoints`. + type: string + serviceName: + description: |- + ServiceName is the name of the Kubernetes Service. + Cross-namespace references are not supported—ensure the ApisixRoute + and the Service are in the same namespace. + type: string + servicePort: + anyOf: + - type: integer + - type: string + description: |- + ServicePort is the port of the Kubernetes Service. + This can be either the port name or port number. + x-kubernetes-int-or-string: true + subset: + description: |- + Subset specifies a named subset of the target Service. + The subset must be pre-defined in the corresponding ApisixUpstream resource. + type: string + required: + - serviceName + - servicePort + type: object + match: + description: Match defines the criteria used to match incoming + TCP or UDP connections. + properties: + host: + description: Host is the destination host address used to + match the incoming TCP/UDP traffic. + type: string + ingressPort: + description: |- + IngressPort is the port on which the APISIX Ingress proxy server listens. + This must be a statically configured port, as APISIX does not support dynamic port binding. + format: int32 + type: integer + required: + - ingressPort + type: object + name: + description: Name is a unique identifier for the route. This + field must not be empty. + type: string + plugins: + description: Plugins defines a list of plugins to apply to this + route. + items: + description: ApisixRoutePlugin represents an APISIX plugin. + properties: + config: + description: Plugin configuration. + x-kubernetes-preserve-unknown-fields: true + enable: + default: true + description: Whether this plugin is in use, default is + true. + type: boolean + name: + description: The plugin name. + type: string + secretRef: + description: Plugin configuration secretRef. + type: string + required: + - enable + - name + type: object + type: array + protocol: + description: Protocol specifies the L4 protocol to match. Can + be `tcp` or `udp`. + type: string + required: + - backend + - match + - name + - protocol + type: object + type: array + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixtlses.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixTls + listKind: ApisixTlsList + plural: apisixtlses + shortNames: + - atls + singular: apisixtls + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hosts + name: SNIs + type: string + - jsonPath: .spec.secret.name + name: Secret Name + type: string + - jsonPath: .spec.secret.namespace + name: Secret Namespace + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.client.ca.name + name: Client CA Secret Name + type: string + - jsonPath: .spec.client.ca.namespace + name: Client CA Secret Namespace + type: string + name: v2 + schema: + openAPIV3Schema: + description: ApisixTls defines configuration for TLS and mutual TLS (mTLS). + 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: ApisixTlsSpec defines the TLS configuration. + properties: + client: + description: Client defines mutual TLS (mTLS) settings, such as the + CA certificate and verification depth. + properties: + caSecret: + description: CASecret references the secret containing the CA + certificate for client certificate validation. + properties: + name: + description: Name is the name of the Kubernetes Secret. + minLength: 1 + type: string + namespace: + description: Namespace is the namespace where the Kubernetes + Secret is located. + minLength: 1 + type: string + required: + - name + - namespace + type: object + depth: + description: Depth specifies the maximum verification depth for + the client certificate chain. + type: integer + skip_mtls_uri_regex: + description: SkipMTLSUriRegex contains RegEx patterns for URIs + to skip mutual TLS verification. + items: + type: string + type: array + type: object + hosts: + description: |- + Hosts lists the SNI (Server Name Indication) hostnames that this TLS configuration applies to. + Must contain at least one host. + items: + pattern: ^\*?[0-9a-zA-Z-.]+$ + type: string + minItems: 1 + type: array + ingressClassName: + description: |- + IngressClassName specifies which IngressClass this resource is associated with. + The APISIX controller only processes this resource if the class matches its own. + type: string + secret: + description: |- + Secret refers to the Kubernetes TLS secret containing the certificate and private key. + This secret must exist in the specified namespace and contain valid TLS data. + properties: + name: + description: Name is the name of the Kubernetes Secret. + minLength: 1 + type: string + namespace: + description: Namespace is the namespace where the Kubernetes Secret + is located. + minLength: 1 + type: string + required: + - name + - namespace + type: object + required: + - hosts + - secret + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: apisixupstreams.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: ApisixUpstream + listKind: ApisixUpstreamList + plural: apisixupstreams + shortNames: + - au + singular: apisixupstream + scope: Namespaced + versions: + - name: v2 + schema: + openAPIV3Schema: + description: ApisixUpstream defines configuration for upstream services. + 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: ApisixUpstreamSpec defines the upstream configuration. + properties: + discovery: + description: |- + Discovery configures service discovery for the upstream. + Deprecated: no longer supported in standalone mode. + properties: + args: + additionalProperties: + type: string + description: |- + Args contains additional configuration parameters required by the discovery provider. + These are passed as key-value pairs. + type: object + serviceName: + description: ServiceName is the name of the service to discover. + type: string + type: + description: Type is the name of the service discovery provider. + type: string + required: + - serviceName + - type + type: object + externalNodes: + description: |- + ExternalNodes defines a static list of backend nodes located outside the cluster. + When this field is set, the upstream will route traffic directly to these nodes + without DNS resolution or service discovery. + items: + description: |- + ApisixUpstreamExternalNode defines configuration for an external upstream node. + This allows referencing services outside the cluster. + properties: + name: + description: Name is the hostname or IP address of the external + node. + type: string + port: + description: Port specifies the port number on which the external + node is accepting traffic. + type: integer + type: + description: Type indicates the kind of external node. Can be + `Domain`, or `Service`. + type: string + weight: + description: |- + Weight defines the load balancing weight of this node. + Higher values increase the share of traffic sent to this node. + type: integer + type: object + minItems: 1 + type: array + healthCheck: + description: |- + HealthCheck defines the active and passive health check configuration for the upstream. + Deprecated: no longer supported in standalone mode. + properties: + active: + description: Active health checks proactively send requests to + upstream nodes to determine their availability. + properties: + concurrency: + description: Concurrency sets the number of targets to be + checked at the same time. + minimum: 0 + type: integer + healthy: + description: Healthy configures the rules that define an upstream + node as healthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status codes + that are considered healthy. + items: + type: integer + minItems: 1 + type: array + interval: + description: Interval defines the time interval for checking + targets, in seconds. + type: string + successes: + description: Successes define the number of successful + probes to define a healthy target. + maximum: 254 + minimum: 0 + type: integer + type: object + host: + description: Host sets the upstream host. + type: string + httpPath: + description: HTTPPath sets the HTTP probe request path. + type: string + port: + description: Port sets the upstream port. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + requestHeaders: + description: RequestHeaders sets the request headers. + items: + type: string + type: array + strictTLS: + description: StrictTLS sets whether to enforce TLS. + type: boolean + timeout: + description: Timeout sets health check timeout in seconds. + format: int64 + type: integer + type: + description: Type is the health check type. Can be `http`, + `https`, or `tcp`. + enum: + - http + - https + - tcp + type: string + unhealthy: + description: Unhealthy configures the rules that define an + upstream node as unhealthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status codes + that are considered unhealthy. + items: + type: integer + minItems: 1 + type: array + httpFailures: + description: HTTPFailures define the number of HTTP failures + to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + interval: + description: Interval defines the time interval for checking + targets, in seconds. + type: string + tcpFailures: + description: TCPFailures define the number of TCP failures + to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + timeout: + description: Timeout sets health check timeout in seconds. + type: integer + type: object + type: object + passive: + description: Passive health checks evaluate upstream health based + on observed traffic, such as timeouts or errors. + properties: + healthy: + description: Healthy defines the conditions under which an + upstream node is considered healthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status codes + that are considered healthy. + items: + type: integer + minItems: 1 + type: array + successes: + description: Successes define the number of successful + probes to define a healthy target. + maximum: 254 + minimum: 0 + type: integer + type: object + type: + description: |- + Type specifies the type of passive health check. + Can be `http`, `https`, or `tcp`. + type: string + unhealthy: + description: Unhealthy defines the conditions under which + an upstream node is considered unhealthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status codes + that are considered unhealthy. + items: + type: integer + minItems: 1 + type: array + httpFailures: + description: HTTPFailures define the number of HTTP failures + to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + tcpFailures: + description: TCPFailures define the number of TCP failures + to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + timeout: + description: Timeout sets health check timeout in seconds. + type: integer + type: object + type: object + required: + - active + type: object + ingressClassName: + description: |- + IngressClassName is the name of an IngressClass cluster resource. + Controller implementations use this field to determine whether they + should process this ApisixUpstream resource. + type: string + loadbalancer: + description: LoadBalancer specifies the load balancer configuration + for Kubernetes Service. + properties: + hashOn: + default: vars + description: |- + HashOn specified the type of field used for hashing, required when type is `chash`. + Default is `vars`. Can be `vars`, `header`, `cookie`, `consumer`, or `vars_combinations`. + enum: + - vars + - header + - cookie + - consumer + - vars_combinations + type: string + key: + description: |- + Key is used with HashOn, generally required when type is `chash`. + When HashOn is `header` or `cookie`, specifies the name of the header or cookie. + When HashOn is `consumer`, key is not required, as the consumer name is used automatically. + When HashOn is `vars` or `vars_combinations`, key refers to one or a combination of + [built-in variables](/enterprise/reference/built-in-variables). + type: string + type: + default: roundrobin + description: |- + Type specifies the load balancing algorithms to route traffic to the backend. + Default is `roundrobin`. + Can be `roundrobin`, `chash`, `ewma`, or `least_conn`. + enum: + - roundrobin + - chash + - ewma + - least_conn + type: string + required: + - type + type: object + passHost: + description: |- + PassHost configures how the host header should be determined when a + request is forwarded to the upstream. + Default is `pass`. + Can be `pass`, `node` or `rewrite`: + * `pass`: preserve the original Host header + * `node`: use the upstream node’s host + * `rewrite`: set to a custom host via upstreamHost + enum: + - pass + - node + - rewrite + type: string + portLevelSettings: + description: |- + PortLevelSettings allows fine-grained upstream configuration for specific ports, + useful when a backend service exposes multiple ports with different behaviors or protocols. + items: + description: |- + PortLevelSettings configures the ApisixUpstreamConfig for each individual port. It inherits + configuration from the outer level (the whole Kubernetes Service) and overrides some of + them if they are set on the port level. + properties: + discovery: + description: |- + Discovery configures service discovery for the upstream. + Deprecated: no longer supported in standalone mode. + properties: + args: + additionalProperties: + type: string + description: |- + Args contains additional configuration parameters required by the discovery provider. + These are passed as key-value pairs. + type: object + serviceName: + description: ServiceName is the name of the service to discover. + type: string + type: + description: Type is the name of the service discovery provider. + type: string + required: + - serviceName + - type + type: object + healthCheck: + description: |- + HealthCheck defines the active and passive health check configuration for the upstream. + Deprecated: no longer supported in standalone mode. + properties: + active: + description: Active health checks proactively send requests + to upstream nodes to determine their availability. + properties: + concurrency: + description: Concurrency sets the number of targets + to be checked at the same time. + minimum: 0 + type: integer + healthy: + description: Healthy configures the rules that define + an upstream node as healthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status + codes that are considered healthy. + items: + type: integer + minItems: 1 + type: array + interval: + description: Interval defines the time interval + for checking targets, in seconds. + type: string + successes: + description: Successes define the number of successful + probes to define a healthy target. + maximum: 254 + minimum: 0 + type: integer + type: object + host: + description: Host sets the upstream host. + type: string + httpPath: + description: HTTPPath sets the HTTP probe request path. + type: string + port: + description: Port sets the upstream port. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + requestHeaders: + description: RequestHeaders sets the request headers. + items: + type: string + type: array + strictTLS: + description: StrictTLS sets whether to enforce TLS. + type: boolean + timeout: + description: Timeout sets health check timeout in seconds. + format: int64 + type: integer + type: + description: Type is the health check type. Can be `http`, + `https`, or `tcp`. + enum: + - http + - https + - tcp + type: string + unhealthy: + description: Unhealthy configures the rules that define + an upstream node as unhealthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status + codes that are considered unhealthy. + items: + type: integer + minItems: 1 + type: array + httpFailures: + description: HTTPFailures define the number of HTTP + failures to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + interval: + description: Interval defines the time interval + for checking targets, in seconds. + type: string + tcpFailures: + description: TCPFailures define the number of TCP + failures to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + timeout: + description: Timeout sets health check timeout in + seconds. + type: integer + type: object + type: object + passive: + description: Passive health checks evaluate upstream health + based on observed traffic, such as timeouts or errors. + properties: + healthy: + description: Healthy defines the conditions under which + an upstream node is considered healthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status + codes that are considered healthy. + items: + type: integer + minItems: 1 + type: array + successes: + description: Successes define the number of successful + probes to define a healthy target. + maximum: 254 + minimum: 0 + type: integer + type: object + type: + description: |- + Type specifies the type of passive health check. + Can be `http`, `https`, or `tcp`. + type: string + unhealthy: + description: Unhealthy defines the conditions under + which an upstream node is considered unhealthy. + properties: + httpCodes: + description: HTTPCodes define a list of HTTP status + codes that are considered unhealthy. + items: + type: integer + minItems: 1 + type: array + httpFailures: + description: HTTPFailures define the number of HTTP + failures to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + tcpFailures: + description: TCPFailures define the number of TCP + failures to define an unhealthy target. + maximum: 254 + minimum: 0 + type: integer + timeout: + description: Timeout sets health check timeout in + seconds. + type: integer + type: object + type: object + required: + - active + type: object + loadbalancer: + description: LoadBalancer specifies the load balancer configuration + for Kubernetes Service. + properties: + hashOn: + default: vars + description: |- + HashOn specified the type of field used for hashing, required when type is `chash`. + Default is `vars`. Can be `vars`, `header`, `cookie`, `consumer`, or `vars_combinations`. + enum: + - vars + - header + - cookie + - consumer + - vars_combinations + type: string + key: + description: |- + Key is used with HashOn, generally required when type is `chash`. + When HashOn is `header` or `cookie`, specifies the name of the header or cookie. + When HashOn is `consumer`, key is not required, as the consumer name is used automatically. + When HashOn is `vars` or `vars_combinations`, key refers to one or a combination of + [built-in variables](/enterprise/reference/built-in-variables). + type: string + type: + default: roundrobin + description: |- + Type specifies the load balancing algorithms to route traffic to the backend. + Default is `roundrobin`. + Can be `roundrobin`, `chash`, `ewma`, or `least_conn`. + enum: + - roundrobin + - chash + - ewma + - least_conn + type: string + required: + - type + type: object + passHost: + description: |- + PassHost configures how the host header should be determined when a + request is forwarded to the upstream. + Default is `pass`. + Can be `pass`, `node` or `rewrite`: + * `pass`: preserve the original Host header + * `node`: use the upstream node’s host + * `rewrite`: set to a custom host via upstreamHost + enum: + - pass + - node + - rewrite + type: string + port: + description: Port is a Kubernetes Service port. + format: int32 + type: integer + retries: + description: |- + Retries defines the number of retry attempts APISIX should make when a failure occurs. + Failures include timeouts, network errors, or 5xx status codes. + format: int64 + type: integer + scheme: + description: |- + Scheme is the protocol used to communicate with the upstream. + Default is `http`. + Can be `http`, `https`, `grpc`, or `grpcs`. + enum: + - http + - https + - grpc + - grpcs + type: string + subsets: + description: |- + Subsets defines labeled subsets of service endpoints, typically used for + service versioning or canary deployments. + items: + description: ApisixUpstreamSubset defines a single endpoints + group of one Service. + properties: + labels: + additionalProperties: + type: string + description: Labels is the label set of this subset. + type: object + name: + description: Name is the name of subset. + type: string + required: + - labels + - name + type: object + type: array + timeout: + description: Timeout specifies the connection, send, and read + timeouts for upstream requests. + properties: + connect: + description: Connect timeout for establishing a connection + to the upstream. + type: string + read: + description: Read timeout for reading data from the upstream. + type: string + send: + description: Send timeout for sending data to the upstream. + type: string + type: object + tlsSecret: + description: |- + TLSSecret references a Kubernetes Secret that contains the client certificate and key + for mutual TLS when connecting to the upstream. + properties: + name: + description: Name is the name of the Kubernetes Secret. + minLength: 1 + type: string + namespace: + description: Namespace is the namespace where the Kubernetes + Secret is located. + minLength: 1 + type: string + required: + - name + - namespace + type: object + upstreamHost: + description: UpstreamHost sets a custom Host header when passHost + is set to `rewrite`. + type: string + required: + - port + type: object + type: array + retries: + description: |- + Retries defines the number of retry attempts APISIX should make when a failure occurs. + Failures include timeouts, network errors, or 5xx status codes. + format: int64 + type: integer + scheme: + description: |- + Scheme is the protocol used to communicate with the upstream. + Default is `http`. + Can be `http`, `https`, `grpc`, or `grpcs`. + enum: + - http + - https + - grpc + - grpcs + type: string + subsets: + description: |- + Subsets defines labeled subsets of service endpoints, typically used for + service versioning or canary deployments. + items: + description: ApisixUpstreamSubset defines a single endpoints group + of one Service. + properties: + labels: + additionalProperties: + type: string + description: Labels is the label set of this subset. + type: object + name: + description: Name is the name of subset. + type: string + required: + - labels + - name + type: object + type: array + timeout: + description: Timeout specifies the connection, send, and read timeouts + for upstream requests. + properties: + connect: + description: Connect timeout for establishing a connection to + the upstream. + type: string + read: + description: Read timeout for reading data from the upstream. + type: string + send: + description: Send timeout for sending data to the upstream. + type: string + type: object + tlsSecret: + description: |- + TLSSecret references a Kubernetes Secret that contains the client certificate and key + for mutual TLS when connecting to the upstream. + properties: + name: + description: Name is the name of the Kubernetes Secret. + minLength: 1 + type: string + namespace: + description: Namespace is the namespace where the Kubernetes Secret + is located. + minLength: 1 + type: string + required: + - name + - namespace + type: object + upstreamHost: + description: UpstreamHost sets a custom Host header when passHost + is set to `rewrite`. + type: string + type: object + status: + description: ApisixStatus is the status report for Apisix ingress Resources + properties: + conditions: + 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 + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: gatewayproxies.apisix.apache.org +spec: + group: apisix.apache.org + names: + kind: GatewayProxy + listKind: GatewayProxyList + plural: gatewayproxies + singular: gatewayproxy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GatewayProxy defines configuration for the gateway proxy instances + used to route traffic to services. + 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: |- + GatewayProxySpec defines configuration of gateway proxy instances, + including networking settings, global plugins, and plugin metadata. + properties: + pluginMetadata: + additionalProperties: + x-kubernetes-preserve-unknown-fields: true + description: PluginMetadata configures common configuration shared + by all plugin instances of the same name. + type: object + plugins: + description: Plugins configure global plugins. + items: + description: GatewayProxyPlugin contains plugin configuration. + properties: + config: + description: Config defines the plugin's configuration details. + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Enabled defines whether the plugin is enabled. + type: boolean + name: + description: Name is the name of the plugin. + type: string + type: object + type: array + provider: + description: Provider configures the provider details. + properties: + controlPlane: + description: ControlPlane specifies the configuration for control + plane provider. + properties: + auth: + description: Auth specifies the authentication configuration. + properties: + adminKey: + description: AdminKey specifies the admin key authentication + configuration. + properties: + value: + description: Value sets the admin key value explicitly + (not recommended for production). + type: string + valueFrom: + description: ValueFrom specifies the source of the + admin key. + properties: + secretKeyRef: + description: SecretKeyRef references a key in + a Secret. + properties: + key: + description: Key is the key in the secret + to retrieve the secret from. + type: string + name: + description: Name is the name of the secret. + type: string + required: + - key + - name + type: object + type: object + type: object + type: + description: |- + Type specifies the type of authentication. + Can only be `AdminKey`. + enum: + - AdminKey + type: string + required: + - type + type: object + endpoints: + description: Endpoints specifies the list of control plane + endpoints. + items: + type: string + minItems: 1 + type: array + service: + properties: + name: + description: Name is the name of the provider. + type: string + port: + description: Port is the port of the provider. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + tlsVerify: + description: TlsVerify specifies whether to verify the TLS + certificate of the control plane. + type: boolean + required: + - auth + type: object + type: + description: Type specifies the type of provider. Can only be + `ControlPlane`. + enum: + - ControlPlane + type: string + required: + - type + type: object + publishService: + description: |- + PublishService specifies the LoadBalancer-type Service whose external address the controller uses to + update the status of Ingress resources. + type: string + statusAddress: + description: |- + StatusAddress specifies the external IP addresses that the controller uses to populate the status field + of GatewayProxy or Ingress resources for developers to access. + items: + type: string + type: array + type: object + type: object + served: true + storage: true diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go index daee40856..d48e397f5 100644 --- a/internal/manager/controllers.go +++ b/internal/manager/controllers.go @@ -20,8 +20,9 @@ package manager import ( "context" + "github.com/go-logr/logr" netv1 "k8s.io/api/networking/v1" - "k8s.io/api/networking/v1beta1" + netv1beta1 "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -39,7 +40,6 @@ import ( "github.com/apache/apisix-ingress-controller/internal/provider" types "github.com/apache/apisix-ingress-controller/internal/types" "github.com/apache/apisix-ingress-controller/pkg/utils" - "github.com/go-logr/logr" ) // K8s @@ -108,7 +108,7 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro icgv := netv1.SchemeGroupVersion if !utils.HasAPIResource(mgr, &netv1.IngressClass{}) { setupLog.Info("IngressClass v1 not found, falling back to IngressClass v1beta1") - icgv = v1beta1.SchemeGroupVersion + icgv = netv1beta1.SchemeGroupVersion controllers = append(controllers, &controller.IngressClassV1beta1Reconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -246,7 +246,7 @@ func registerReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManage func registerV2ForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager, log logr.Logger) { icgv := netv1.SchemeGroupVersion if !utils.HasAPIResource(mgr, &netv1.IngressClass{}) { - icgv = v1beta1.SchemeGroupVersion + icgv = netv1beta1.SchemeGroupVersion } gvks := []schema.GroupVersionKind{ @@ -269,6 +269,7 @@ func registerV2ForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessM return ingressClass != nil }), }) + log.Info("Registered v1alpha1 GVKs for readiness checks", "gvks", gvks) } func registerGatewayAPIForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager, log logr.Logger) { @@ -283,6 +284,7 @@ func registerGatewayAPIForReadinessGVK(mgr manager.Manager, readier readiness.Re readier.RegisterGVK(readiness.GVKConfig{ GVKs: gvks, }) + log.Info("Registered v1alpha1 GVKs for readiness checks", "gvks", gvks) } func registerV1alpha1ForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager, log logr.Logger) { @@ -309,4 +311,5 @@ func registerV1alpha1ForReadinessGVK(mgr manager.Manager, readier readiness.Read return controller.MatchConsumerGatewayRef(context.Background(), c, log, consumer) }), }) + log.Info("Registered v1alpha1 GVKs for readiness checks", "gvks", gvks) } From da6bffa10b944d2f39505bdab3cbe60892d0341b Mon Sep 17 00:00:00 2001 From: AlinsRan Date: Mon, 4 Aug 2025 09:19:13 +0800 Subject: [PATCH 12/13] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/manager/controllers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go index d48e397f5..42b148c3f 100644 --- a/internal/manager/controllers.go +++ b/internal/manager/controllers.go @@ -269,7 +269,7 @@ func registerV2ForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessM return ingressClass != nil }), }) - log.Info("Registered v1alpha1 GVKs for readiness checks", "gvks", gvks) + log.Info("Registered v2 GVKs for readiness checks", "gvks", gvks) } func registerGatewayAPIForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager, log logr.Logger) { @@ -284,7 +284,7 @@ func registerGatewayAPIForReadinessGVK(mgr manager.Manager, readier readiness.Re readier.RegisterGVK(readiness.GVKConfig{ GVKs: gvks, }) - log.Info("Registered v1alpha1 GVKs for readiness checks", "gvks", gvks) + log.Info("Registered Gateway API GVKs for readiness checks", "gvks", gvks) } func registerV1alpha1ForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessManager, log logr.Logger) { From 69e4a75cb124f5b99494ae5335e746d1226be58b Mon Sep 17 00:00:00 2001 From: rongxin Date: Mon, 4 Aug 2025 10:31:15 +0800 Subject: [PATCH 13/13] resolve comments --- .../controller/ingressclass_v1beta1_controller.go | 7 +++++-- internal/controller/utils.go | 11 ----------- internal/manager/controllers.go | 2 +- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/internal/controller/ingressclass_v1beta1_controller.go b/internal/controller/ingressclass_v1beta1_controller.go index 78a59cdcd..70aa86ca1 100644 --- a/internal/controller/ingressclass_v1beta1_controller.go +++ b/internal/controller/ingressclass_v1beta1_controller.go @@ -187,13 +187,16 @@ func (r *IngressClassV1beta1Reconciler) processInfrastructure(tctx *provider.Tra return nil } + // Since v1beta1 does not support specifying the target namespace, + // and GatewayProxy is a namespace-scoped resource, we default to using + // the "default" namespace for convenience. namespace := "default" if IngressClassV1beta1.Spec.Parameters.Namespace != nil { namespace = *IngressClassV1beta1.Spec.Parameters.Namespace } gatewayProxy := new(v1alpha1.GatewayProxy) - if err := r.Get(context.Background(), client.ObjectKey{ + if err := r.Get(tctx, client.ObjectKey{ Namespace: namespace, Name: IngressClassV1beta1.Spec.Parameters.Name, }, gatewayProxy); err != nil { @@ -212,7 +215,7 @@ func (r *IngressClassV1beta1Reconciler) processInfrastructure(tctx *provider.Tra if auth.AdminKey.ValueFrom.SecretKeyRef != nil { secretRef := auth.AdminKey.ValueFrom.SecretKeyRef secret := &corev1.Secret{} - if err := r.Get(context.Background(), client.ObjectKey{ + if err := r.Get(tctx, client.ObjectKey{ Namespace: namespace, Name: secretRef.Name, }, secret); err != nil { diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 564825e9d..7df386283 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -41,7 +41,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" k8stypes "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" @@ -1571,16 +1570,6 @@ func MatchConsumerGatewayRef(ctx context.Context, c client.Client, log logr.Logg return matchesController(string(gatewayClass.Spec.ControllerName)) } -func GetIngressClassName(obj client.Object) string { - switch t := obj.(type) { - case *networkingv1.Ingress: - return ptr.Deref(t.Spec.IngressClassName, "") - case *networkingv1beta1.IngressClass: - return "" - } - return "" -} - // watchEndpointSliceOrEndpoints adds watcher for EndpointSlice or Endpoints based on cluster API support func watchEndpointSliceOrEndpoints(bdr *ctrl.Builder, supportsEndpointSlice bool, endpointSliceMapFunc, endpointsMapFunc handler.MapFunc, log logr.Logger) *ctrl.Builder { if supportsEndpointSlice { diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go index 42b148c3f..0414c08fb 100644 --- a/internal/manager/controllers.go +++ b/internal/manager/controllers.go @@ -257,7 +257,7 @@ func registerV2ForReadinessGVK(mgr manager.Manager, readier readiness.ReadinessM types.GvkOf(&apiv2.ApisixConsumer{}), } if utils.HasAPIResource(mgr, &netv1.Ingress{}) { - gvks = append(gvks, types.GvkOf(&netv1.IngressClass{})) + gvks = append(gvks, types.GvkOf(&netv1.Ingress{})) } c := mgr.GetClient()