Skip to content

Commit

Permalink
feat: add api for ingress resource
Browse files Browse the repository at this point in the history
Signed-off-by: warjiang <1096409085@qq.com>
  • Loading branch information
warjiang committed Oct 21, 2024
1 parent 213e49c commit a449392
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/api/app/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/cronjob"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/daemonset"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/deployment"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/ingress"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/job"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/namespace"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/overview"
Expand Down
40 changes: 40 additions & 0 deletions cmd/api/app/routes/ingress/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ingress

import (
"github.com/gin-gonic/gin"
"github.com/karmada-io/dashboard/cmd/api/app/router"
"github.com/karmada-io/dashboard/cmd/api/app/types/common"
"github.com/karmada-io/dashboard/pkg/client"
"github.com/karmada-io/dashboard/pkg/resource/ingress"
)

func handleGetIngress(c *gin.Context) {
k8sClient := client.InClusterClientForKarmadaApiServer()
dataSelect := common.ParseDataSelectPathParameter(c)
nsQuery := common.ParseNamespacePathParameter(c)
result, err := ingress.GetIngressList(k8sClient, nsQuery, dataSelect)
if err != nil {
common.Fail(c, err)
return
}
common.Success(c, result)
}

func handleGetIngressDetail(c *gin.Context) {
k8sClient := client.InClusterClientForKarmadaApiServer()
namespace := c.Param("namespace")
name := c.Param("service")
result, err := ingress.GetIngressDetail(k8sClient, namespace, name)
if err != nil {
common.Fail(c, err)
return
}
common.Success(c, result)
}

func init() {
r := router.V1()
r.GET("/ingress", handleGetIngress)
r.GET("/ingress/:namespace", handleGetIngress)
r.GET("/ingress/:namespace/:service", handleGetIngressDetail)
}
40 changes: 40 additions & 0 deletions pkg/resource/ingress/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ingress

import (
"github.com/karmada-io/dashboard/pkg/dataselect"
v1 "k8s.io/api/networking/v1"
)

// The code below allows to perform complex data section on []extensions.Ingress

type IngressCell v1.Ingress

func (self IngressCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
switch name {
case dataselect.NameProperty:
return dataselect.StdComparableString(self.ObjectMeta.Name)
case dataselect.CreationTimestampProperty:
return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
case dataselect.NamespaceProperty:
return dataselect.StdComparableString(self.ObjectMeta.Namespace)
default:
// if name is not supported then just return a constant dummy value, sort will have no effect.
return nil
}
}

func toCells(std []v1.Ingress) []dataselect.DataCell {
cells := make([]dataselect.DataCell, len(std))
for i := range std {
cells[i] = IngressCell(std[i])
}
return cells
}

func fromCells(cells []dataselect.DataCell) []v1.Ingress {
std := make([]v1.Ingress, len(cells))
for i := range std {
std[i] = v1.Ingress(cells[i].(IngressCell))
}
return std
}
47 changes: 47 additions & 0 deletions pkg/resource/ingress/detail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ingress

import (
"context"
"log"

v1 "k8s.io/api/networking/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
client "k8s.io/client-go/kubernetes"
)

// IngressDetail API resource provides mechanisms to inject containers with configuration data while keeping
// containers agnostic of Kubernetes
type IngressDetail struct {
// Extends list item structure.
Ingress `json:",inline"`

// Spec is the desired state of the Ingress.
Spec v1.IngressSpec `json:"spec"`

// Status is the current state of the Ingress.
Status v1.IngressStatus `json:"status"`

// List of non-critical errors, that occurred during resource retrieval.
Errors []error `json:"errors"`
}

// GetIngressDetail returns detailed information about an ingress
func GetIngressDetail(client client.Interface, namespace, name string) (*IngressDetail, error) {
log.Printf("Getting details of %s ingress in %s namespace", name, namespace)

rawIngress, err := client.NetworkingV1().Ingresses(namespace).Get(context.TODO(), name, metaV1.GetOptions{})

if err != nil {
return nil, err
}

return getIngressDetail(rawIngress), nil
}

func getIngressDetail(i *v1.Ingress) *IngressDetail {
return &IngressDetail{
Ingress: toIngress(i),
Spec: i.Spec,
Status: i.Status,
}
}
50 changes: 50 additions & 0 deletions pkg/resource/ingress/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ingress

import (
"github.com/karmada-io/dashboard/pkg/common/types"
networkingv1 "k8s.io/api/networking/v1"
)

func FilterIngressByService(ingresses []networkingv1.Ingress, serviceName string) []networkingv1.Ingress {
var matchingIngresses []networkingv1.Ingress
for _, ingress := range ingresses {
if ingressMatchesServiceName(ingress, serviceName) {
matchingIngresses = append(matchingIngresses, ingress)
}
}
return matchingIngresses
}

func ingressMatchesServiceName(ingress networkingv1.Ingress, serviceName string) bool {
spec := ingress.Spec
if ingressBackendMatchesServiceName(spec.DefaultBackend, serviceName) {
return true
}

for _, rule := range spec.Rules {
if rule.IngressRuleValue.HTTP == nil {
continue
}
for _, path := range rule.IngressRuleValue.HTTP.Paths {
if ingressBackendMatchesServiceName(&path.Backend, serviceName) {
return true
}
}
}
return false
}

func ingressBackendMatchesServiceName(ingressBackend *networkingv1.IngressBackend, serviceName string) bool {
if ingressBackend == nil {
return false
}

if ingressBackend.Service != nil && ingressBackend.Service.Name == serviceName {
return true
}

if ingressBackend.Resource != nil && ingressBackend.Resource.Kind == types.ResourceKindService && ingressBackend.Resource.Name == serviceName {
return true
}
return false
}
104 changes: 104 additions & 0 deletions pkg/resource/ingress/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package ingress

import (
"context"
"github.com/karmada-io/dashboard/pkg/common/errors"
"github.com/karmada-io/dashboard/pkg/common/helpers"
"github.com/karmada-io/dashboard/pkg/common/types"
"github.com/karmada-io/dashboard/pkg/dataselect"
"github.com/karmada-io/dashboard/pkg/resource/common"
v1 "k8s.io/api/networking/v1"
client "k8s.io/client-go/kubernetes"
)

// Ingress - a single ingress returned to the frontend.
type Ingress struct {
types.ObjectMeta `json:"objectMeta"`
types.TypeMeta `json:"typeMeta"`

// External endpoints of this ingress.
Endpoints []common.Endpoint `json:"endpoints"`
Hosts []string `json:"hosts"`
}

// IngressList - response structure for a queried ingress list.
type IngressList struct {
types.ListMeta `json:"listMeta"`

// Unordered list of Ingresss.
Items []Ingress `json:"items"`

// List of non-critical errors, that occurred during resource retrieval.
Errors []error `json:"errors"`
}

// GetIngressList returns all ingresses in the given namespace.
func GetIngressList(client client.Interface, namespace *common.NamespaceQuery,
dsQuery *dataselect.DataSelectQuery) (*IngressList, error) {
ingressList, err := client.NetworkingV1().Ingresses(namespace.ToRequestParam()).List(context.TODO(), helpers.ListEverything)

nonCriticalErrors, criticalError := errors.ExtractErrors(err)
if criticalError != nil {
return nil, criticalError
}

return ToIngressList(ingressList.Items, nonCriticalErrors, dsQuery), nil
}

func getEndpoints(ingress *v1.Ingress) []common.Endpoint {
endpoints := make([]common.Endpoint, 0)
if len(ingress.Status.LoadBalancer.Ingress) > 0 {
for _, status := range ingress.Status.LoadBalancer.Ingress {
endpoint := common.Endpoint{}
if status.Hostname != "" {
endpoint.Host = status.Hostname
} else if status.IP != "" {
endpoint.Host = status.IP
}
endpoints = append(endpoints, endpoint)
}
}
return endpoints
}

func getHosts(ingress *v1.Ingress) []string {
hosts := make([]string, 0)
set := make(map[string]struct{})

for _, rule := range ingress.Spec.Rules {
if _, exists := set[rule.Host]; !exists && len(rule.Host) > 0 {
hosts = append(hosts, rule.Host)
}

set[rule.Host] = struct{}{}
}

return hosts
}

func toIngress(ingress *v1.Ingress) Ingress {
return Ingress{
ObjectMeta: types.NewObjectMeta(ingress.ObjectMeta),
TypeMeta: types.NewTypeMeta(types.ResourceKindIngress),
Endpoints: getEndpoints(ingress),
Hosts: getHosts(ingress),
}
}

func ToIngressList(ingresses []v1.Ingress, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *IngressList {
newIngressList := &IngressList{
ListMeta: types.ListMeta{TotalItems: len(ingresses)},
Items: make([]Ingress, 0),
Errors: nonCriticalErrors,
}

ingresCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toCells(ingresses), dsQuery)
ingresses = fromCells(ingresCells)
newIngressList.ListMeta = types.ListMeta{TotalItems: filteredTotal}

for _, ingress := range ingresses {
newIngressList.Items = append(newIngressList.Items, toIngress(&ingress))
}

return newIngressList
}

0 comments on commit a449392

Please sign in to comment.