diff --git a/.gitignore b/.gitignore index f36e22271..d7a932a47 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ dist .tmp api7-ingress-controller api7-ingress-controller-conformance-report.yaml + +*.mdx diff --git a/api/adc/types.go b/api/adc/types.go index a7eea8225..978b844d5 100644 --- a/api/adc/types.go +++ b/api/adc/types.go @@ -540,6 +540,6 @@ type ResponseDetails struct { } type ResponseData struct { - Value map[string]interface{} `json:"value"` - ErrorMsg string `json:"error_msg"` + Value map[string]any `json:"value"` + ErrorMsg string `json:"error_msg"` } diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3cad6be02..3b79829c4 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -133,3 +133,27 @@ rules: verbs: - get - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - update + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - get + - update diff --git a/config/samples/config.yaml b/config/samples/config.yaml index f67e0bfa6..2543d85f9 100644 --- a/config/samples/config.yaml +++ b/config/samples/config.yaml @@ -8,6 +8,10 @@ controller_name: gateway.api7.io/api7-ingress-controller # The controller name leader_election_id: "api7-ingress-controller-leader" # The leader election ID for the API7 Ingress Controller. # The default value is "api7-ingress-controller-leader". +ingress_class: api7 # The ingress class name of the API7 Ingress Controller. +ingress_publish_service: "" # The service name of the ingress publish service. +ingress_status_address: [] # The status address of the ingress. + gateway_configs: # The configuration of the API7 Gateway. - name: api7 # The name of the Gateway in the Gateway API. control_plane: diff --git a/internal/controller/config/config.go b/internal/controller/config/config.go index 9dced46f6..09ca8e2d2 100644 --- a/internal/controller/config/config.go +++ b/internal/controller/config/config.go @@ -28,6 +28,7 @@ func NewDefaultConfig() *Config { LeaderElectionID: DefaultLeaderElectionID, ProbeAddr: DefaultProbeAddr, MetricsAddr: DefaultMetricsAddr, + IngressClass: DefaultIngressClass, } } @@ -161,3 +162,19 @@ func GatewayNameList() []string { } return gatewayNameList } + +func GetIngressClass() string { + return ControllerConfig.IngressClass +} + +func GetIngressPublishService() string { + return ControllerConfig.IngressPublishService +} + +func GetIngressStatusAddress() []string { + return ControllerConfig.IngressStatusAddress +} + +func GetControllerName() string { + return ControllerConfig.ControllerName +} diff --git a/internal/controller/config/types.go b/internal/controller/config/types.go index ff6da259b..497815cc0 100644 --- a/internal/controller/config/types.go +++ b/internal/controller/config/types.go @@ -13,6 +13,9 @@ const ( // DefaultLogLevel is the default log level for apisix-ingress-controller. DefaultLogLevel = "info" + // DefaultIngressClass is the default ingress class name for Ingress resources + DefaultIngressClass = "api7" + DefaultMetricsAddr = ":8080" DefaultProbeAddr = ":8081" ) @@ -20,16 +23,19 @@ const ( // Config contains all config items which are necessary for // apisix-ingress-controller's running. type Config struct { - CertFilePath string `json:"cert_file" yaml:"cert_file"` - KeyFilePath string `json:"key_file" yaml:"key_file"` - LogLevel string `json:"log_level" yaml:"log_level"` - ControllerName string `json:"controller_name" yaml:"controller_name"` - LeaderElectionID string `json:"leader_election_id" yaml:"leader_election_id"` - GatewayConfigs []*GatewayConfig `json:"gateway_configs" yaml:"gateway_configs"` - MetricsAddr string `json:"metrics_addr" yaml:"metrics_addr"` - EnableHTTP2 bool `json:"enable_http2" yaml:"enable_http2"` - ProbeAddr string `json:"probe_addr" yaml:"probe_addr"` - SecureMetrics bool `json:"secure_metrics" yaml:"secure_metrics"` + CertFilePath string `json:"cert_file" yaml:"cert_file"` + KeyFilePath string `json:"key_file" yaml:"key_file"` + LogLevel string `json:"log_level" yaml:"log_level"` + ControllerName string `json:"controller_name" yaml:"controller_name"` + LeaderElectionID string `json:"leader_election_id" yaml:"leader_election_id"` + GatewayConfigs []*GatewayConfig `json:"gateway_configs" yaml:"gateway_configs"` + MetricsAddr string `json:"metrics_addr" yaml:"metrics_addr"` + EnableHTTP2 bool `json:"enable_http2" yaml:"enable_http2"` + ProbeAddr string `json:"probe_addr" yaml:"probe_addr"` + SecureMetrics bool `json:"secure_metrics" yaml:"secure_metrics"` + IngressClass string `json:"ingress_class" yaml:"ingress_class"` + IngressPublishService string `json:"ingress_publish_service" yaml:"ingress_publish_service"` + IngressStatusAddress []string `json:"ingress_status_address" yaml:"ingress_status_address"` } type GatewayConfig struct { diff --git a/internal/controller/indexer/indexer.go b/internal/controller/indexer/indexer.go index 58872e086..05ab2f590 100644 --- a/internal/controller/indexer/indexer.go +++ b/internal/controller/indexer/indexer.go @@ -3,6 +3,7 @@ package indexer import ( "context" + networkingv1 "k8s.io/api/networking/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" @@ -13,6 +14,9 @@ const ( ExtensionRef = "extensionRef" ParametersRef = "parametersRef" ParentRefs = "parentRefs" + IngressClass = "ingressClass" + SecretIndexRef = "secretRefs" + IngressClassRef = "ingressClassRef" ) func SetupIndexer(mgr ctrl.Manager) error { @@ -22,6 +26,9 @@ func SetupIndexer(mgr ctrl.Manager) error { if err := setupHTTPRouteIndexer(mgr); err != nil { return err } + if err := setupIngressIndexer(mgr); err != nil { + return err + } return nil } @@ -67,6 +74,102 @@ func setupHTTPRouteIndexer(mgr ctrl.Manager) error { return nil } +func setupIngressIndexer(mgr ctrl.Manager) error { + // create IngressClass index + if err := mgr.GetFieldIndexer().IndexField( + context.Background(), + &networkingv1.Ingress{}, + IngressClassRef, + IngressClassRefIndexFunc, + ); err != nil { + return err + } + + // create Service index for quick lookup of Ingresses using specific services + if err := mgr.GetFieldIndexer().IndexField( + context.Background(), + &networkingv1.Ingress{}, + ServiceIndexRef, + IngressServiceIndexFunc, + ); err != nil { + return err + } + + // create secret index for TLS + if err := mgr.GetFieldIndexer().IndexField( + context.Background(), + &networkingv1.Ingress{}, + SecretIndexRef, + IngressSecretIndexFunc, + ); err != nil { + return err + } + + // create IngressClass index + if err := mgr.GetFieldIndexer().IndexField( + context.Background(), + &networkingv1.IngressClass{}, + IngressClass, + IngressClassIndexFunc, + ); err != nil { + return err + } + + return nil +} + +func IngressClassIndexFunc(rawObj client.Object) []string { + ingressClass := rawObj.(*networkingv1.IngressClass) + if ingressClass.Spec.Controller == "" { + return nil + } + controllerName := ingressClass.Spec.Controller + return []string{controllerName} +} + +func IngressClassRefIndexFunc(rawObj client.Object) []string { + ingress := rawObj.(*networkingv1.Ingress) + if ingress.Spec.IngressClassName == nil { + return nil + } + return []string{*ingress.Spec.IngressClassName} +} + +func IngressServiceIndexFunc(rawObj client.Object) []string { + ingress := rawObj.(*networkingv1.Ingress) + var services []string + + for _, rule := range ingress.Spec.Rules { + if rule.HTTP == nil { + continue + } + + for _, path := range rule.HTTP.Paths { + if path.Backend.Service == nil { + continue + } + key := GenIndexKey(ingress.Namespace, path.Backend.Service.Name) + services = append(services, key) + } + } + return services +} + +func IngressSecretIndexFunc(rawObj client.Object) []string { + ingress := rawObj.(*networkingv1.Ingress) + secrets := make([]string, 0) + + for _, tls := range ingress.Spec.TLS { + if tls.SecretName == "" { + continue + } + + key := GenIndexKey(ingress.Namespace, tls.SecretName) + secrets = append(secrets, key) + } + return secrets +} + func GenIndexKey(namespace, name string) string { return client.ObjectKey{ Namespace: namespace, diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go new file mode 100644 index 000000000..d5fc617ff --- /dev/null +++ b/internal/controller/ingress_controller.go @@ -0,0 +1,420 @@ +package controller + +import ( + "context" + "fmt" + "reflect" + + "github.com/api7/api7-ingress-controller/internal/controller/config" + "github.com/api7/api7-ingress-controller/internal/controller/indexer" + "github.com/api7/api7-ingress-controller/internal/provider" + "github.com/api7/gopkg/pkg/log" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" + networkingv1 "k8s.io/api/networking/v1" + 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" +) + +// IngressReconciler reconciles a Ingress object. +type IngressReconciler struct { //nolint:revive + client.Client + Scheme *runtime.Scheme + Log logr.Logger + + Provider provider.Provider +} + +// SetupWithManager sets up the controller with the Manager. +func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&networkingv1.Ingress{}, + builder.WithPredicates( + predicate.NewPredicateFuncs(r.checkIngressClass), + ), + ). + WithEventFilter(predicate.GenerationChangedPredicate{}). + Watches( + &networkingv1.IngressClass{}, + handler.EnqueueRequestsFromMapFunc(r.listIngressForIngressClass), + builder.WithPredicates( + predicate.NewPredicateFuncs(r.matchesIngressController), + ), + ). + Watches( + &discoveryv1.EndpointSlice{}, + handler.EnqueueRequestsFromMapFunc(r.listIngressesByService), + ). + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.listIngressesBySecret), + ). + Complete(r) +} + +// Reconcile handles the reconciliation of Ingress resources +func (r *IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + ingress := new(networkingv1.Ingress) + if err := r.Get(ctx, req.NamespacedName, ingress); err != nil { + if client.IgnoreNotFound(err) == nil { + // Ingress was deleted, clean up corresponding resources + ingress.Namespace = req.Namespace + ingress.Name = req.Name + + ingress.TypeMeta = metav1.TypeMeta{ + Kind: KindIngress, + APIVersion: networkingv1.SchemeGroupVersion.String(), + } + + if err := r.Provider.Delete(ctx, ingress); err != nil { + r.Log.Error(err, "failed to delete ingress resources", "ingress", ingress.Name) + return ctrl.Result{}, err + } + r.Log.Info("deleted ingress resources", "ingress", ingress.Name) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + r.Log.Info("reconciling ingress", "ingress", ingress.Name) + + // create a translate context + tctx := provider.NewDefaultTranslateContext() + + // process TLS configuration + if err := r.processTLS(ctx, tctx, ingress); err != nil { + r.Log.Error(err, "failed to process TLS configuration", "ingress", ingress.Name) + return ctrl.Result{}, err + } + + // process backend services + if err := r.processBackends(ctx, tctx, ingress); err != nil { + r.Log.Error(err, "failed to process backend services", "ingress", ingress.Name) + return ctrl.Result{}, err + } + + // update the ingress resources + if err := r.Provider.Update(ctx, tctx, ingress); err != nil { + r.Log.Error(err, "failed to update ingress resources", "ingress", ingress.Name) + return ctrl.Result{}, err + } + + // update the ingress status + if err := r.updateStatus(ctx, ingress); err != nil { + r.Log.Error(err, "failed to update ingress status", "ingress", ingress.Name) + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// checkIngressClass check if the ingress uses the ingress class that we control +func (r *IngressReconciler) checkIngressClass(obj client.Object) bool { + ingress := obj.(*networkingv1.Ingress) + + if ingress.Spec.IngressClassName == nil { + // handle the case where IngressClassName is not specified + // find all ingress classes and check if any of them is marked as default + 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 + } + + configuredClass := config.GetIngressClass() + // if the ingress class name matches the configured ingress class name, return true + if *ingress.Spec.IngressClassName == configuredClass { + return true + } + + // if it does not match, check if the ingress class is controlled by us + ingressClass := networkingv1.IngressClass{} + if err := r.Client.Get(context.Background(), client.ObjectKey{Name: *ingress.Spec.IngressClassName}, &ingressClass); err != nil { + return false + } + + return matchesController(ingressClass.Spec.Controller) +} + +// matchesIngressController check if the ingress class is controlled by us +func (r *IngressReconciler) matchesIngressController(obj client.Object) bool { + ingressClass, ok := obj.(*networkingv1.IngressClass) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to IngressClass") + return false + } + + return matchesController(ingressClass.Spec.Controller) +} + +// listIngressForIngressClass list all ingresses that use a specific ingress class +func (r *IngressReconciler) listIngressForIngressClass(ctx context.Context, ingressClass client.Object) []reconcile.Request { + ingressList := &networkingv1.IngressList{} + if err := r.List(ctx, ingressList, client.MatchingFields{ + indexer.IngressClassRef: ingressClass.GetName(), + }); err != nil { + r.Log.Error(err, "failed to list ingresses for ingress class", "ingressclass", ingressClass.GetName()) + return nil + } + + requests := make([]reconcile.Request, 0, len(ingressList.Items)) + for _, ingress := range ingressList.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Namespace: ingress.Namespace, + Name: ingress.Name, + }, + }) + } + return requests +} + +// listIngressesByService list all ingresses that use a specific service +func (r *IngressReconciler) listIngressesByService(ctx context.Context, obj client.Object) []reconcile.Request { + endpointSlice, ok := obj.(*discoveryv1.EndpointSlice) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to EndpointSlice") + return nil + } + + namespace := endpointSlice.GetNamespace() + serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName] + + 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 { + 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) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to Secret") + return nil + } + + namespace := secret.GetNamespace() + name := secret.GetName() + + ingressList := &networkingv1.IngressList{} + if err := r.List(ctx, ingressList, client.MatchingFields{ + indexer.SecretIndexRef: indexer.GenIndexKey(namespace, name), + }); err != nil { + r.Log.Error(err, "failed to list ingresses by secret", "secret", name) + return nil + } + + requests := make([]reconcile.Request, 0, len(ingressList.Items)) + for _, ingress := range ingressList.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Namespace: ingress.Namespace, + Name: ingress.Name, + }, + }) + } + return requests +} + +// processTLS process the TLS configuration of the ingress +func (r *IngressReconciler) processTLS(ctx context.Context, tctx *provider.TranslateContext, ingress *networkingv1.Ingress) error { + for _, tls := range ingress.Spec.TLS { + if tls.SecretName == "" { + continue + } + + secret := corev1.Secret{} + if err := r.Get(ctx, client.ObjectKey{ + Namespace: ingress.Namespace, + Name: tls.SecretName, + }, &secret); err != nil { + log.Error(err, "failed to get secret", "namespace", ingress.Namespace, "name", tls.SecretName) + return err + } + + // add the secret to the translate context + tctx.Secrets[types.NamespacedName{Namespace: ingress.Namespace, Name: tls.SecretName}] = &secret + } + + return nil +} + +// processBackends process the backend services of the ingress +func (r *IngressReconciler) processBackends(ctx context.Context, tctx *provider.TranslateContext, ingress *networkingv1.Ingress) error { + var terr error + + // process all the backend services in the rules + for _, rule := range ingress.Spec.Rules { + if rule.HTTP == nil { + continue + } + + for _, path := range rule.HTTP.Paths { + if path.Backend.Service == nil { + continue + } + + service := path.Backend.Service + if err := r.processBackendService(ctx, tctx, ingress.Namespace, service); err != nil { + terr = err + } + } + } + + return terr +} + +// processBackendService process a single backend service +func (r *IngressReconciler) processBackendService(ctx context.Context, tctx *provider.TranslateContext, namespace string, backendService *networkingv1.IngressServiceBackend) error { + // get the service + var service corev1.Service + if err := r.Get(ctx, client.ObjectKey{ + Namespace: namespace, + Name: backendService.Name, + }, &service); err != nil { + if client.IgnoreNotFound(err) == nil { + r.Log.Info("service not found", "namespace", namespace, "name", backendService.Name) + return nil + } + return err + } + + // verify if the port exists + var portExists bool + if backendService.Port.Number != 0 { + for _, servicePort := range service.Spec.Ports { + if servicePort.Port == backendService.Port.Number { + portExists = true + break + } + } + } else if backendService.Port.Name != "" { + for _, servicePort := range service.Spec.Ports { + if servicePort.Name == backendService.Port.Name { + portExists = true + break + } + } + } + + if !portExists { + err := fmt.Errorf("port(name: %s, number: %d) not found in service %s/%s", backendService.Port.Name, backendService.Port.Number, namespace, backendService.Name) + r.Log.Error(err, "service port not found") + return err + } + + // get the endpoint slices + endpointSliceList := &discoveryv1.EndpointSliceList{} + if err := r.List(ctx, 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[client.ObjectKey{ + Namespace: namespace, + Name: backendService.Name, + }] = endpointSliceList.Items + + return nil +} + +// updateStatus update the status of the ingress +func (r *IngressReconciler) updateStatus(ctx context.Context, ingress *networkingv1.Ingress) error { + var loadBalancerStatus networkingv1.IngressLoadBalancerStatus + + // 1. use the IngressStatusAddress in the config + statusAddresses := config.GetIngressStatusAddress() + if len(statusAddresses) > 0 { + for _, addr := range statusAddresses { + if addr == "" { + continue + } + loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ + IP: addr, + }) + } + } else { + // 2. if the IngressStatusAddress is not configured, try to use the PublishService + publishService := config.GetIngressPublishService() + if publishService != "" { + // parse the namespace/name format + namespace, name, err := SplitMetaNamespaceKey(publishService) + if err != nil { + return fmt.Errorf("invalid ingress-publish-service format: %s, expected format: namespace/name", publishService) + } + if namespace == "" { + namespace = ingress.Namespace + } + + svc := &corev1.Service{} + if err := r.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, svc); err != nil { + return fmt.Errorf("failed to get publish service %s: %w", publishService, err) + } + + if svc.Spec.Type == corev1.ServiceTypeLoadBalancer { + // get the LoadBalancer IP and Hostname of the service + for _, ip := range svc.Status.LoadBalancer.Ingress { + if ip.IP != "" { + loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ + IP: ip.IP, + }) + } + if ip.Hostname != "" { + loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ + Hostname: ip.Hostname, + }) + } + } + } + } + } + + // update the load balancer status + if len(loadBalancerStatus.Ingress) > 0 && !reflect.DeepEqual(ingress.Status.LoadBalancer, loadBalancerStatus) { + ingress.Status.LoadBalancer = loadBalancerStatus + return r.Status().Update(ctx, ingress) + } + + return nil +} diff --git a/internal/controller/utils.go b/internal/controller/utils.go index d20746288..9282117ea 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -8,6 +8,7 @@ import ( "github.com/api7/api7-ingress-controller/internal/controller/config" "github.com/samber/lo" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" @@ -19,8 +20,19 @@ const ( KindGateway = "Gateway" KindHTTPRoute = "HTTPRoute" KindGatewayClass = "GatewayClass" + KindIngress = "Ingress" ) +const defaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class" + +// 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" + } + return false +} + func acceptedMessage(kind string) string { return fmt.Sprintf("the %s has been accepted by the api7-ingress-controller", kind) } @@ -731,3 +743,19 @@ func getListenerStatus( return statusArray, nil } + +// SplitMetaNamespaceKey returns the namespace and name that +// MetaNamespaceKeyFunc encoded into key. +func SplitMetaNamespaceKey(key string) (namespace, name string, err error) { + parts := strings.Split(key, "/") + switch len(parts) { + case 1: + // name only, no namespace + return "", parts[0], nil + case 2: + // namespace and name + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected key format: %q", key) +} diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go index e2846da09..813483c1f 100644 --- a/internal/manager/controllers.go +++ b/internal/manager/controllers.go @@ -33,6 +33,11 @@ import ( // +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes,verbs=get;list;watch // +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes/status,verbs=get;update +// Networking +// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;update +// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get;update +// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingressclasses,verbs=get;list;watch + type Controller interface { SetupWithManager(mgr manager.Manager) error } @@ -59,6 +64,12 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("HTTPRoute"), Provider: pro, }, + &controller.IngressReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Ingress"), + Provider: pro, + }, &controller.ConsumerReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/internal/provider/adc/adc.go b/internal/provider/adc/adc.go index b0ebf2c0e..5d1674d9b 100644 --- a/internal/provider/adc/adc.go +++ b/internal/provider/adc/adc.go @@ -10,6 +10,7 @@ import ( "go.uber.org/zap" "gopkg.in/yaml.v3" + networkingv1 "k8s.io/api/networking/v1" "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" @@ -60,6 +61,9 @@ func (d *adcClient) Update(ctx context.Context, tctx *provider.TranslateContext, case *gatewayv1.Gateway: result, err = d.translator.TranslateGateway(tctx, t.DeepCopy()) resourceTypes = append(resourceTypes, "global_rule", "ssl", "plugin_metadata") + case *networkingv1.Ingress: + result, err = d.translator.TranslateIngress(tctx, t.DeepCopy()) + resourceTypes = append(resourceTypes, "service", "ssl") } if err != nil { return err diff --git a/internal/provider/adc/translator/ingress.go b/internal/provider/adc/translator/ingress.go new file mode 100644 index 000000000..38b3d5e73 --- /dev/null +++ b/internal/provider/adc/translator/ingress.go @@ -0,0 +1,10 @@ +package translator + +import ( + "github.com/api7/api7-ingress-controller/internal/provider" + networkingv1 "k8s.io/api/networking/v1" +) + +func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *networkingv1.Ingress) (*TranslateResult, error) { + return nil, nil +} diff --git a/test/e2e/framework/manifests/ingress.yaml b/test/e2e/framework/manifests/ingress.yaml index b57c988cc..0a7296821 100644 --- a/test/e2e/framework/manifests/ingress.yaml +++ b/test/e2e/framework/manifests/ingress.yaml @@ -184,6 +184,30 @@ rules: verbs: - get - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - update + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - get + - update --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole