Skip to content

Commit e2c5898

Browse files
authored
Merge pull request #141 from warjiang/feature/ingress-api
Add api for ingress resource
2 parents 9a7b9a2 + a449392 commit e2c5898

File tree

6 files changed

+282
-0
lines changed

6 files changed

+282
-0
lines changed

cmd/api/app/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/cronjob"
2424
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/daemonset"
2525
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/deployment"
26+
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/ingress"
2627
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/job"
2728
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/namespace"
2829
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/overview"

cmd/api/app/routes/ingress/handler.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ingress
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
"github.com/karmada-io/dashboard/cmd/api/app/router"
6+
"github.com/karmada-io/dashboard/cmd/api/app/types/common"
7+
"github.com/karmada-io/dashboard/pkg/client"
8+
"github.com/karmada-io/dashboard/pkg/resource/ingress"
9+
)
10+
11+
func handleGetIngress(c *gin.Context) {
12+
k8sClient := client.InClusterClientForKarmadaApiServer()
13+
dataSelect := common.ParseDataSelectPathParameter(c)
14+
nsQuery := common.ParseNamespacePathParameter(c)
15+
result, err := ingress.GetIngressList(k8sClient, nsQuery, dataSelect)
16+
if err != nil {
17+
common.Fail(c, err)
18+
return
19+
}
20+
common.Success(c, result)
21+
}
22+
23+
func handleGetIngressDetail(c *gin.Context) {
24+
k8sClient := client.InClusterClientForKarmadaApiServer()
25+
namespace := c.Param("namespace")
26+
name := c.Param("service")
27+
result, err := ingress.GetIngressDetail(k8sClient, namespace, name)
28+
if err != nil {
29+
common.Fail(c, err)
30+
return
31+
}
32+
common.Success(c, result)
33+
}
34+
35+
func init() {
36+
r := router.V1()
37+
r.GET("/ingress", handleGetIngress)
38+
r.GET("/ingress/:namespace", handleGetIngress)
39+
r.GET("/ingress/:namespace/:service", handleGetIngressDetail)
40+
}

pkg/resource/ingress/common.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ingress
2+
3+
import (
4+
"github.com/karmada-io/dashboard/pkg/dataselect"
5+
v1 "k8s.io/api/networking/v1"
6+
)
7+
8+
// The code below allows to perform complex data section on []extensions.Ingress
9+
10+
type IngressCell v1.Ingress
11+
12+
func (self IngressCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
13+
switch name {
14+
case dataselect.NameProperty:
15+
return dataselect.StdComparableString(self.ObjectMeta.Name)
16+
case dataselect.CreationTimestampProperty:
17+
return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
18+
case dataselect.NamespaceProperty:
19+
return dataselect.StdComparableString(self.ObjectMeta.Namespace)
20+
default:
21+
// if name is not supported then just return a constant dummy value, sort will have no effect.
22+
return nil
23+
}
24+
}
25+
26+
func toCells(std []v1.Ingress) []dataselect.DataCell {
27+
cells := make([]dataselect.DataCell, len(std))
28+
for i := range std {
29+
cells[i] = IngressCell(std[i])
30+
}
31+
return cells
32+
}
33+
34+
func fromCells(cells []dataselect.DataCell) []v1.Ingress {
35+
std := make([]v1.Ingress, len(cells))
36+
for i := range std {
37+
std[i] = v1.Ingress(cells[i].(IngressCell))
38+
}
39+
return std
40+
}

pkg/resource/ingress/detail.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package ingress
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
v1 "k8s.io/api/networking/v1"
8+
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
client "k8s.io/client-go/kubernetes"
10+
)
11+
12+
// IngressDetail API resource provides mechanisms to inject containers with configuration data while keeping
13+
// containers agnostic of Kubernetes
14+
type IngressDetail struct {
15+
// Extends list item structure.
16+
Ingress `json:",inline"`
17+
18+
// Spec is the desired state of the Ingress.
19+
Spec v1.IngressSpec `json:"spec"`
20+
21+
// Status is the current state of the Ingress.
22+
Status v1.IngressStatus `json:"status"`
23+
24+
// List of non-critical errors, that occurred during resource retrieval.
25+
Errors []error `json:"errors"`
26+
}
27+
28+
// GetIngressDetail returns detailed information about an ingress
29+
func GetIngressDetail(client client.Interface, namespace, name string) (*IngressDetail, error) {
30+
log.Printf("Getting details of %s ingress in %s namespace", name, namespace)
31+
32+
rawIngress, err := client.NetworkingV1().Ingresses(namespace).Get(context.TODO(), name, metaV1.GetOptions{})
33+
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
return getIngressDetail(rawIngress), nil
39+
}
40+
41+
func getIngressDetail(i *v1.Ingress) *IngressDetail {
42+
return &IngressDetail{
43+
Ingress: toIngress(i),
44+
Spec: i.Spec,
45+
Status: i.Status,
46+
}
47+
}

pkg/resource/ingress/filter.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package ingress
2+
3+
import (
4+
"github.com/karmada-io/dashboard/pkg/common/types"
5+
networkingv1 "k8s.io/api/networking/v1"
6+
)
7+
8+
func FilterIngressByService(ingresses []networkingv1.Ingress, serviceName string) []networkingv1.Ingress {
9+
var matchingIngresses []networkingv1.Ingress
10+
for _, ingress := range ingresses {
11+
if ingressMatchesServiceName(ingress, serviceName) {
12+
matchingIngresses = append(matchingIngresses, ingress)
13+
}
14+
}
15+
return matchingIngresses
16+
}
17+
18+
func ingressMatchesServiceName(ingress networkingv1.Ingress, serviceName string) bool {
19+
spec := ingress.Spec
20+
if ingressBackendMatchesServiceName(spec.DefaultBackend, serviceName) {
21+
return true
22+
}
23+
24+
for _, rule := range spec.Rules {
25+
if rule.IngressRuleValue.HTTP == nil {
26+
continue
27+
}
28+
for _, path := range rule.IngressRuleValue.HTTP.Paths {
29+
if ingressBackendMatchesServiceName(&path.Backend, serviceName) {
30+
return true
31+
}
32+
}
33+
}
34+
return false
35+
}
36+
37+
func ingressBackendMatchesServiceName(ingressBackend *networkingv1.IngressBackend, serviceName string) bool {
38+
if ingressBackend == nil {
39+
return false
40+
}
41+
42+
if ingressBackend.Service != nil && ingressBackend.Service.Name == serviceName {
43+
return true
44+
}
45+
46+
if ingressBackend.Resource != nil && ingressBackend.Resource.Kind == types.ResourceKindService && ingressBackend.Resource.Name == serviceName {
47+
return true
48+
}
49+
return false
50+
}

pkg/resource/ingress/list.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package ingress
2+
3+
import (
4+
"context"
5+
"github.com/karmada-io/dashboard/pkg/common/errors"
6+
"github.com/karmada-io/dashboard/pkg/common/helpers"
7+
"github.com/karmada-io/dashboard/pkg/common/types"
8+
"github.com/karmada-io/dashboard/pkg/dataselect"
9+
"github.com/karmada-io/dashboard/pkg/resource/common"
10+
v1 "k8s.io/api/networking/v1"
11+
client "k8s.io/client-go/kubernetes"
12+
)
13+
14+
// Ingress - a single ingress returned to the frontend.
15+
type Ingress struct {
16+
types.ObjectMeta `json:"objectMeta"`
17+
types.TypeMeta `json:"typeMeta"`
18+
19+
// External endpoints of this ingress.
20+
Endpoints []common.Endpoint `json:"endpoints"`
21+
Hosts []string `json:"hosts"`
22+
}
23+
24+
// IngressList - response structure for a queried ingress list.
25+
type IngressList struct {
26+
types.ListMeta `json:"listMeta"`
27+
28+
// Unordered list of Ingresss.
29+
Items []Ingress `json:"items"`
30+
31+
// List of non-critical errors, that occurred during resource retrieval.
32+
Errors []error `json:"errors"`
33+
}
34+
35+
// GetIngressList returns all ingresses in the given namespace.
36+
func GetIngressList(client client.Interface, namespace *common.NamespaceQuery,
37+
dsQuery *dataselect.DataSelectQuery) (*IngressList, error) {
38+
ingressList, err := client.NetworkingV1().Ingresses(namespace.ToRequestParam()).List(context.TODO(), helpers.ListEverything)
39+
40+
nonCriticalErrors, criticalError := errors.ExtractErrors(err)
41+
if criticalError != nil {
42+
return nil, criticalError
43+
}
44+
45+
return ToIngressList(ingressList.Items, nonCriticalErrors, dsQuery), nil
46+
}
47+
48+
func getEndpoints(ingress *v1.Ingress) []common.Endpoint {
49+
endpoints := make([]common.Endpoint, 0)
50+
if len(ingress.Status.LoadBalancer.Ingress) > 0 {
51+
for _, status := range ingress.Status.LoadBalancer.Ingress {
52+
endpoint := common.Endpoint{}
53+
if status.Hostname != "" {
54+
endpoint.Host = status.Hostname
55+
} else if status.IP != "" {
56+
endpoint.Host = status.IP
57+
}
58+
endpoints = append(endpoints, endpoint)
59+
}
60+
}
61+
return endpoints
62+
}
63+
64+
func getHosts(ingress *v1.Ingress) []string {
65+
hosts := make([]string, 0)
66+
set := make(map[string]struct{})
67+
68+
for _, rule := range ingress.Spec.Rules {
69+
if _, exists := set[rule.Host]; !exists && len(rule.Host) > 0 {
70+
hosts = append(hosts, rule.Host)
71+
}
72+
73+
set[rule.Host] = struct{}{}
74+
}
75+
76+
return hosts
77+
}
78+
79+
func toIngress(ingress *v1.Ingress) Ingress {
80+
return Ingress{
81+
ObjectMeta: types.NewObjectMeta(ingress.ObjectMeta),
82+
TypeMeta: types.NewTypeMeta(types.ResourceKindIngress),
83+
Endpoints: getEndpoints(ingress),
84+
Hosts: getHosts(ingress),
85+
}
86+
}
87+
88+
func ToIngressList(ingresses []v1.Ingress, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *IngressList {
89+
newIngressList := &IngressList{
90+
ListMeta: types.ListMeta{TotalItems: len(ingresses)},
91+
Items: make([]Ingress, 0),
92+
Errors: nonCriticalErrors,
93+
}
94+
95+
ingresCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toCells(ingresses), dsQuery)
96+
ingresses = fromCells(ingresCells)
97+
newIngressList.ListMeta = types.ListMeta{TotalItems: filteredTotal}
98+
99+
for _, ingress := range ingresses {
100+
newIngressList.Items = append(newIngressList.Items, toIngress(&ingress))
101+
}
102+
103+
return newIngressList
104+
}

0 commit comments

Comments
 (0)