Files
kubesphere/pkg/kapis/gateway/v1alpha2/handler.go
2025-04-30 15:53:51 +08:00

181 lines
4.3 KiB
Go

/*
* Copyright 2024 the KubeSphere Authors.
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package v1alpha2
import (
"context"
"strings"
"github.com/emicklei/go-restful/v3"
jsonpatch "github.com/evanphx/json-patch/v5"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
"kubesphere.io/api/gateway/v1alpha2"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
"kubesphere.io/kubesphere/pkg/api"
)
const (
MasterLabel = "node-role.kubernetes.io/control-plane"
SvcNameAnnotation = "gateway.kubesphere.io/service-name"
)
type handler struct {
cache runtimeclient.Reader
}
func (h *handler) ListIngressClassScopes(req *restful.Request, resp *restful.Response) {
currentNs := req.PathParameter("namespace")
ctx := req.Request.Context()
ingressClassScopeList := v1alpha2.IngressClassScopeList{}
err := h.cache.List(ctx, &ingressClassScopeList)
if err != nil {
api.HandleError(resp, req, err)
return
}
var ret []v1alpha2.IngressClassScope
for _, item := range ingressClassScopeList.Items {
namespaces := item.Spec.Scope.Namespaces
nsSelector := item.Spec.Scope.NamespaceSelector
// Specify all namespace
if len(namespaces) == 0 && nsSelector == "" {
_ = h.setStatus(ctx, &item)
ret = append(ret, item)
continue
}
// Specify namespaces
if len(namespaces) > 0 {
for _, n := range namespaces {
if n == currentNs {
_ = h.setStatus(ctx, &item)
ret = append(ret, item)
break
}
}
continue
}
// Specify namespaceSelector
if nsSelector != "" {
nsList := corev1.NamespaceList{}
_ = h.cache.List(ctx, &nsList, &runtimeclient.ListOptions{LabelSelector: Selector(nsSelector)})
for _, n := range nsList.Items {
if n.Name == currentNs {
_ = h.setStatus(ctx, &item)
ret = append(ret, item)
break
}
}
}
}
resp.WriteEntity(ret)
}
func Selector(s string) labels.Selector {
if selector, err := labels.Parse(s); err != nil {
return labels.Everything()
} else {
return selector
}
}
func (h *handler) getMasterNodeIp(ctx context.Context) []string {
internalIps := []string{}
masters := &corev1.NodeList{}
err := h.cache.List(ctx, masters, &runtimeclient.ListOptions{
LabelSelector: labels.SelectorFromSet(
labels.Set{
MasterLabel: "",
})})
if err != nil {
klog.Info(err)
return internalIps
}
for _, node := range masters.Items {
for _, address := range node.Status.Addresses {
if address.Type == corev1.NodeInternalIP {
internalIps = append(internalIps, address.Address)
}
}
}
return internalIps
}
func (h *handler) setStatus(ctx context.Context, ics *v1alpha2.IngressClassScope) (e error) {
svcKeyStr, exists := ics.Annotations[SvcNameAnnotation]
if !exists {
klog.Errorf("Name: %s, Annotation %s not found", ics.Name, SvcNameAnnotation)
return nil
}
svcKeyParts := strings.SplitN(svcKeyStr, "/", 2)
if len(svcKeyParts) != 2 {
klog.Errorf("Name: %s, Invalid %s annotation, should follow the namespace/name format", ics.Name, SvcNameAnnotation)
return nil
}
svc := corev1.Service{}
key := types.NamespacedName{
Namespace: svcKeyParts[0],
Name: svcKeyParts[1],
}
if err := h.cache.Get(ctx, key, &svc); err != nil {
klog.Errorf("Failed to fetch svc %s, %v", key.String(), err)
return err
}
// append selected node ip as loadBalancer ingress ip
if svc.Spec.Type != corev1.ServiceTypeLoadBalancer && len(svc.Status.LoadBalancer.Ingress) == 0 {
rips := h.getMasterNodeIp(ctx)
for _, rip := range rips {
gIngress := corev1.LoadBalancerIngress{
IP: rip,
}
svc.Status.LoadBalancer.Ingress = append(svc.Status.LoadBalancer.Ingress, gIngress)
}
}
status := unstructured.Unstructured{
Object: map[string]interface{}{
"loadBalancer": svc.Status.LoadBalancer,
"service": svc.Spec.Ports,
},
}
target, err := status.MarshalJSON()
if err != nil {
return err
}
if ics.Status.Raw != nil {
//merge with origin status
patch, err := jsonpatch.CreateMergePatch([]byte(`{}`), target)
if err != nil {
return err
}
modified, err := jsonpatch.MergePatch(ics.Status.Raw, patch)
if err != nil {
return err
}
ics.Status.Raw = modified
}
ics.Status.Raw = target
return nil
}