Skip to content

Commit

Permalink
Merge pull request #8 from gianlucam76/filters
Browse files Browse the repository at this point in the history
Add filters to GET clusters API
  • Loading branch information
gianlucam76 authored Apr 26, 2024
2 parents 67ce68b + 450faaf commit c72fb64
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 19 deletions.
18 changes: 17 additions & 1 deletion internal/server/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,21 @@ limitations under the License.
package server

var (
GetLimitedClusters = getLimitedClusters
GetClustersInRange = getClustersInRange
)

var (
GetClusterFiltersFromQuery = getClusterFiltersFromQuery
)

func GetNamespaceFilter(f clusterFilters) string {
return f.Namespace
}

func GetNameFilter(f clusterFilters) string {
return f.name
}

func GetLabelFilter(f clusterFilters) string {
return f.labelSelector.String()
}
71 changes: 58 additions & 13 deletions internal/server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import (
"net/http"
"sort"
"strconv"
"strings"
"syscall"

"github.com/gin-gonic/gin"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"

logs "github.com/projectsveltos/libsveltos/lib/logsettings"
)
Expand All @@ -43,46 +45,71 @@ var (

limit, skip := getLimitAndSkipFromQuery(c)
ginLogger.V(logs.LogDebug).Info(fmt.Sprintf("limit %d skip %d", limit, skip))
filters, err := getClusterFiltersFromQuery(c)
if err != nil {
ginLogger.V(logs.LogInfo).Info(fmt.Sprintf("bad request %s: %v", c.Request.URL, err))
_ = c.AbortWithError(http.StatusBadRequest, err)
}
ginLogger.V(logs.LogDebug).Info(fmt.Sprintf("filters: namespace %q name %q labels %q",
filters.Namespace, filters.name, filters.labelSelector))

manager := GetManagerInstance()
clusters := manager.GetManagedCAPIClusters()
managedClusterData := getManagedClusterData(clusters)
managedClusterData := getManagedClusterData(clusters, filters)
sort.Sort(managedClusterData)

result, err := getLimitedClusters(managedClusterData, limit, skip)
result, err := getClustersInRange(managedClusterData, limit, skip)
if err != nil {
ginLogger.V(logs.LogInfo).Info(fmt.Sprintf("bad request %s: %v", c.Request.URL, err))
_ = c.AbortWithError(http.StatusBadRequest, err)
}

response := ClusterResult{
TotalClusters: len(managedClusterData),
ManagedClusters: result,
}

// Return JSON response
c.JSON(http.StatusOK, result)
c.JSON(http.StatusOK, response)
}

getManagedSveltosClusters = func(c *gin.Context) {
ginLogger.V(logs.LogDebug).Info("get managed SveltosClusters")

limit, skip := getLimitAndSkipFromQuery(c)
ginLogger.V(logs.LogDebug).Info(fmt.Sprintf("limit %d skip %d", limit, skip))
filters, err := getClusterFiltersFromQuery(c)
if err != nil {
ginLogger.V(logs.LogInfo).Info(fmt.Sprintf("bad request %s: %v", c.Request.URL, err))
_ = c.AbortWithError(http.StatusBadRequest, err)
}
ginLogger.V(logs.LogDebug).Info(fmt.Sprintf("filters: namespace %q name %q labels %q",
filters.Namespace, filters.name, filters.labelSelector))

manager := GetManagerInstance()
clusters := manager.GetManagedSveltosClusters()
managedClusterData := getManagedClusterData(clusters)
result, err := getLimitedClusters(managedClusterData, limit, skip)
managedClusterData := getManagedClusterData(clusters, filters)
result, err := getClustersInRange(managedClusterData, limit, skip)
if err != nil {
ginLogger.V(logs.LogInfo).Info(fmt.Sprintf("bad request %s: %v", c.Request.URL, err))
_ = c.AbortWithError(http.StatusBadRequest, err)
}

response := ClusterResult{
TotalClusters: len(managedClusterData),
ManagedClusters: result,
}

// Return JSON response
c.JSON(http.StatusOK, result)
c.JSON(http.StatusOK, response)
}
)

func (m *instance) start(ctx context.Context, port string, logger logr.Logger) {
ginLogger = logger

gin.SetMode(gin.ReleaseMode)
r := gin.Default()
gin.SetMode(gin.ReleaseMode)

r.GET("/capiclusters", getManagedCAPIClusters)
r.GET("/sveltosclusters", getManagedSveltosClusters)
Expand All @@ -109,16 +136,34 @@ func (m *instance) start(ctx context.Context, port string, logger logr.Logger) {
}
}

func getManagedClusterData(clusters map[corev1.ObjectReference]ClusterInfo) ManagedClusters {
data := make(ManagedClusters, len(clusters))
i := 0
func getManagedClusterData(clusters map[corev1.ObjectReference]ClusterInfo, filters *clusterFilters,
) ManagedClusters {

data := make(ManagedClusters, 0)
for k := range clusters {
data[i] = ManagedCluster{
if filters.Namespace != "" {
if !strings.Contains(k.Namespace, filters.Namespace) {
continue
}
}

if filters.name != "" {
if !strings.Contains(k.Name, filters.name) {
continue
}
}

if !filters.labelSelector.Empty() {
if !filters.labelSelector.Matches(labels.Set(clusters[k].Labels)) {
continue
}
}

data = append(data, ManagedCluster{
Namespace: k.Namespace,
Name: k.Name,
ClusterInfo: clusters[k],
}
i++
})
}

return data
Expand Down
51 changes: 47 additions & 4 deletions internal/server/managed_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ limitations under the License.
package server_test

import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"sort"

"github.com/gin-gonic/gin"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

Expand Down Expand Up @@ -66,31 +71,69 @@ var _ = Describe("ManageClusters", func() {

limit := 1
skip := 3
result, err := server.GetLimitedClusters(managedClusters, limit, skip)
result, err := server.GetClustersInRange(managedClusters, limit, skip)
Expect(err).To(BeNil())
for i := 0; i < limit; i++ {
Expect(reflect.DeepEqual(result[i], managedClusters[skip+i]))
}

limit = 3
skip = 5
result, err = server.GetLimitedClusters(managedClusters, limit, skip)
result, err = server.GetClustersInRange(managedClusters, limit, skip)
Expect(err).To(BeNil())
for i := 0; i < limit; i++ {
Expect(reflect.DeepEqual(result[i], managedClusters[skip+i]))
}

limit = 3
skip = 9
result, err = server.GetLimitedClusters(managedClusters, limit, skip)
result, err = server.GetClustersInRange(managedClusters, limit, skip)
Expect(err).To(BeNil())
// limit is 3 but skip starts from 9. Original number of clusters is 10. So expect only 1 cluster
Expect(len(result)).To(Equal(1))
Expect(reflect.DeepEqual(result[0], managedClusters[skip]))

limit = 3
skip = 11
_, err = server.GetLimitedClusters(managedClusters, limit, skip)
_, err = server.GetClustersInRange(managedClusters, limit, skip)
Expect(err).ToNot(BeNil())
})

It("getClusterFiltersFromQuery returns cluster filters", func() {
namespace := randomString()
name := randomString()

data := map[string]string{
randomString(): randomString(),
randomString(): randomString(),
"cluster.x-k8s.io/cluster-name": "clusterapi-workload",
}

var encodedLabels string
for k := range data {
if encodedLabels != "" {
encodedLabels += "," // Or another separator like '|'
}
encodedLabels += fmt.Sprintf("%s:%s", url.QueryEscape(k), url.QueryEscape(data[k]))
}

url := fmt.Sprintf("/capiclusters?namespace=%s", namespace)
url += fmt.Sprintf("&name=%s", name)
url += fmt.Sprintf("&labels=%s", encodedLabels)

req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
Expect(err).To(BeNil())
req.Header.Set("Content-Type", "application/json")

gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req

filters, err := server.GetClusterFiltersFromQuery(c)
Expect(err).To(BeNil())

Expect(server.GetNamespaceFilter(*filters)).To(Equal(namespace))
Expect(server.GetNameFilter(*filters)).To(Equal(name))
})
})
40 changes: 39 additions & 1 deletion internal/server/managed_clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ package server

import (
"errors"
"strings"

"github.com/gin-gonic/gin"
"k8s.io/apimachinery/pkg/labels"
)

type ManagedCluster struct {
Expand All @@ -28,6 +32,11 @@ type ManagedCluster struct {

type ManagedClusters []ManagedCluster

type ClusterResult struct {
TotalClusters int `json:"totalClusters"`
ManagedClusters ManagedClusters `json:"managedClusters"`
}

func (s ManagedClusters) Len() int { return len(s) }
func (s ManagedClusters) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s ManagedClusters) Less(i, j int) bool {
Expand All @@ -37,7 +46,7 @@ func (s ManagedClusters) Less(i, j int) bool {
return s[i].Namespace < s[j].Namespace
}

func getLimitedClusters(clusters ManagedClusters, limit, skip int) (ManagedClusters, error) {
func getClustersInRange(clusters ManagedClusters, limit, skip int) (ManagedClusters, error) {
if skip < 0 {
return nil, errors.New("skip cannot be negative")
}
Expand All @@ -57,3 +66,32 @@ func getLimitedClusters(clusters ManagedClusters, limit, skip int) (ManagedClust
// Use slicing to extract the desired sub-slice
return clusters[skip : skip+adjustedLimit], nil
}

type clusterFilters struct {
Namespace string `uri:"namespace"`
name string `uri:"name"`
labelSelector labels.Selector `uri:"labels"`
}

func getClusterFiltersFromQuery(c *gin.Context) (*clusterFilters, error) {
var filters clusterFilters
// Get the values from query parameters
filters.Namespace = c.Query("namespace")
filters.name = c.Query("name")
filters.labelSelector = labels.NewSelector()

lbls := c.Query("labels")

if lbls != "" {
// format is labels=key1:value1_key2:value2
lbls = strings.ReplaceAll(lbls, ":", "=")
lbls = strings.ReplaceAll(lbls, "_", ",")
parsedSelector, err := labels.Parse(lbls)
if err != nil {
return nil, err
}
filters.labelSelector = parsedSelector
}

return &filters, nil
}

0 comments on commit c72fb64

Please sign in to comment.