diff --git a/.github/workflows/apisix-conformance-test.yml b/.github/workflows/apisix-conformance-test.yml index 322ca92d9..b6d3dd50c 100644 --- a/.github/workflows/apisix-conformance-test.yml +++ b/.github/workflows/apisix-conformance-test.yml @@ -86,7 +86,7 @@ jobs: ARCH: amd64 ENABLE_PROXY: "false" BASE_IMAGE_TAG: "debug" - ADC_VERSION: "dev" + # ADC_VERSION: "dev" run: | echo "building images..." make build-image diff --git a/.github/workflows/apisix-e2e-test.yml b/.github/workflows/apisix-e2e-test.yml index 84fed26c2..0fd842be5 100644 --- a/.github/workflows/apisix-e2e-test.yml +++ b/.github/workflows/apisix-e2e-test.yml @@ -86,7 +86,7 @@ jobs: ARCH: amd64 ENABLE_PROXY: "false" BASE_IMAGE_TAG: "debug" - ADC_VERSION: "dev" + # ADC_VERSION: "dev" run: | echo "building images..." make build-image diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go index 0688055d4..e1f19df8f 100644 --- a/internal/manager/controllers.go +++ b/internal/manager/controllers.go @@ -37,6 +37,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/manager/readiness" "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" ) // K8s @@ -97,21 +98,26 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro if err := indexer.SetupIndexer(mgr); err != nil { return nil, err } - return []Controller{ - &controller.GatewayClassReconciler{ + + setupLog := ctrl.LoggerFrom(ctx).WithName("setup") + var controllers []Controller + + // Gateway API Controllers - conditional registration based on API availability + for resource, controller := range map[client.Object]Controller{ + &gatewayv1.GatewayClass{}: &controller.GatewayClassReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("GatewayClass"), Updater: updater, }, - &controller.GatewayReconciler{ + &gatewayv1.Gateway{}: &controller.GatewayReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Gateway"), Provider: pro, Updater: updater, }, - &controller.HTTPRouteReconciler{ + &gatewayv1.HTTPRoute{}: &controller.HTTPRouteReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("HTTPRoute"), @@ -119,18 +125,28 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Updater: updater, Readier: readier, }, - &controller.IngressReconciler{ + &v1alpha1.Consumer{}: &controller.ConsumerReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Ingress"), + Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Consumer"), Provider: pro, Updater: updater, Readier: readier, }, - &controller.ConsumerReconciler{ + } { + 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{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Consumer"), + Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Ingress"), Provider: pro, Updater: updater, Readier: readier, @@ -141,6 +157,14 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("IngressClass"), Provider: pro, }, + // 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, + }, + // APISIX v2 Controllers - always register these as they are core to the controller &controller.ApisixGlobalRuleReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -185,13 +209,10 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixUpstream"), Updater: updater, }, - &controller.GatewayProxyController{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("GatewayProxy"), - Provider: pro, - }, - }, nil + }...) + + setupLog.Info("Controllers setup completed", "total_controllers", len(controllers)) + return controllers, nil } func registerReadinessGVK(c client.Client, readier readiness.ReadinessManager) { diff --git a/pkg/utils/cluster.go b/pkg/utils/cluster.go new file mode 100644 index 000000000..77d7ec48c --- /dev/null +++ b/pkg/utils/cluster.go @@ -0,0 +1,77 @@ +// 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 ( + "github.com/go-logr/logr" + "k8s.io/client-go/discovery" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/apache/apisix-ingress-controller/internal/types" +) + +// HasAPIResource checks if a specific API resource is available in the current cluster. +// It uses the Discovery API to query the cluster's available resources and returns true +// if the resource is found, false otherwise. +func HasAPIResource(mgr ctrl.Manager, obj client.Object) bool { + return HasAPIResourceWithLogger(mgr, obj, ctrl.Log.WithName("api-detection")) +} + +// 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) + groupVersion := gvk.GroupVersion().String() + + logger = logger.WithValues( + "kind", gvk.Kind, + "group", gvk.Group, + "version", gvk.Version, + "groupVersion", groupVersion, + ) + + // Create discovery client + discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig()) + if err != nil { + logger.Info("failed to create discovery client", "error", err) + return false + } + + // Query server resources for the specific group/version + apiResources, err := discoveryClient.ServerResourcesForGroupVersion(groupVersion) + if err != nil { + logger.Info("group/version not available in cluster", "error", err) + return false + } + + // Check if the specific kind exists in the resource list + for _, res := range apiResources.APIResources { + if res.Kind == gvk.Kind { + return true + } + } + + logger.Info("API resource kind not found in group/version") + return false +} + +func FormatGVK(obj client.Object) string { + gvk := types.GvkOf(obj) + return gvk.GroupVersion().String() + "." + gvk.Kind +}