Skip to content

Commit ff6ed97

Browse files
authored
Merge pull request #6 from metalice/adding-filtering
Adding ability to filter api server results
2 parents f6b0e5c + ed760bd commit ff6ed97

File tree

5 files changed

+124
-5
lines changed

5 files changed

+124
-5
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ FROM golang:1.20
22

33
COPY . /app
44
WORKDIR /app
5+
ENV GIN_MODE=release
56
RUN go build -o kubevirt-apiserver-proxy .
67

78
ENTRYPOINT [ "/app/kubevirt-apiserver-proxy"]

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@ require (
2222
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
2323
github.com/modern-go/reflect2 v1.0.2 // indirect
2424
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
25+
github.com/tidwall/gjson v1.14.4 // indirect
26+
github.com/tidwall/match v1.1.1 // indirect
27+
github.com/tidwall/pretty v1.2.1 // indirect
28+
github.com/tidwall/sjson v1.2.5 // indirect
2529
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
2630
github.com/ugorji/go/codec v1.2.9 // indirect
2731
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
2832
golang.org/x/crypto v0.5.0 // indirect
33+
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
2934
golang.org/x/net v0.7.0 // indirect
3035
golang.org/x/sys v0.5.0 // indirect
3136
golang.org/x/text v0.7.0 // indirect

go.sum

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
2323
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
2424
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
2525
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
26+
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
2627
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
2728
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
2829
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -55,6 +56,16 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
5556
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
5657
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
5758
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
59+
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
60+
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
61+
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
62+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
63+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
64+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
65+
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
66+
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
67+
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
68+
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
5869
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
5970
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
6071
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
@@ -63,6 +74,8 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VA
6374
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
6475
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
6576
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
77+
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
78+
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
6679
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
6780
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
6881
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

handlers/handlers.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ package handlers
22

33
import (
44
"crypto/tls"
5-
"encoding/json"
65
"io"
76
"log"
87
"net/http"
98

109
"github.com/gin-gonic/gin"
1110
"github.com/kubevirt-ui/kubevirt-apiserver-proxy/proxy"
11+
"github.com/kubevirt-ui/kubevirt-apiserver-proxy/util"
1212
)
1313

1414
var API_SERVER_URL string = "kubernetes.default.svc"
@@ -69,13 +69,12 @@ func RequestHandler(c *gin.Context) {
6969
}
7070

7171
defer resp.Body.Close()
72-
bodyJson := map[string]interface{}{}
73-
err = json.Unmarshal(bodyBytes, &bodyJson)
72+
filteredJson := util.FilterResponseQuery(bodyBytes, c.Request.URL.Query())
7473

7574
if err != nil {
7675
log.Println("Unable to transform response body to json) ", err.Error())
7776
}
7877

79-
c.JSON(http.StatusOK, bodyJson)
78+
c.JSON(http.StatusOK, filteredJson)
8079

8180
}

util/util.go

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ package util
22

33
import (
44
"encoding/base64"
5+
"encoding/json"
56
"fmt"
7+
"log"
68
"net/http"
9+
"net/url"
710
"strings"
811
"sync"
912
"time"
1013

1114
"github.com/gorilla/websocket"
15+
"github.com/tidwall/gjson"
16+
"golang.org/x/exp/slices"
1217
)
1318

1419
var HeaderBlacklist = []string{"Cookie", "X-CSRFToken"}
@@ -54,7 +59,7 @@ func DecodeSubprotocol(encodedProtocol string) (string, error) {
5459
return string(decodedProtocol), err
5560
}
5661

57-
func CopyMsgs(writeMutex *sync.Mutex, dest, src *websocket.Conn) error {
62+
func CopyMsgs(writeMutex *sync.Mutex, dest *websocket.Conn, src *websocket.Conn) error {
5863
for {
5964
messageType, msg, err := src.ReadMessage()
6065
if err != nil {
@@ -155,3 +160,99 @@ func CreateProxyHeaders(w http.ResponseWriter, r *http.Request) (http.Header, st
155160

156161
return proxiedHeader, subProtocol, nil
157162
}
163+
164+
func labelsIncludes(labels map[string]interface{}, label string) bool {
165+
splitLabel := strings.Split(label, "=")
166+
return labels[splitLabel[0]] == splitLabel[1]
167+
}
168+
169+
func isMigratable(statuses []interface{}, search string) bool {
170+
indexOfItem := slices.IndexFunc(statuses, func(status interface{}) bool {
171+
return status.(map[string]interface{})["type"] == "LiveMigratable" && status.(map[string]interface{})["status"] == "True"
172+
})
173+
isMigrate := indexOfItem != -1
174+
if search == "notMigratable" && isMigrate {
175+
return false
176+
}
177+
178+
if search == "migratable" && !isMigrate {
179+
return false
180+
}
181+
182+
return true
183+
}
184+
185+
func FilterResponseQuery(bodyBytes []byte, query url.Values) map[string]interface{} {
186+
items := gjson.ParseBytes(bodyBytes).Get("items").Array()
187+
filteredJson := []interface{}{}
188+
isFilters := len(query) != 0
189+
if isFilters {
190+
nextItem:
191+
for _, item := range items {
192+
for key, val := range query {
193+
for _, match := range val {
194+
itemValue := item.Get(key)
195+
matches := strings.Split(match, ",")
196+
isMatch := false
197+
for index, search := range matches {
198+
switch typeResult := itemValue.Type.String(); typeResult {
199+
case "JSON":
200+
{
201+
// case of json and all conditions (and) must apply (labels by input)
202+
if key == "status.conditions" {
203+
isMigrate := isMigratable(itemValue.Value().([]interface{}), search)
204+
if !isMigrate {
205+
continue nextItem
206+
}
207+
continue
208+
}
209+
okInclude := labelsIncludes(itemValue.Value().(map[string]interface{}), search)
210+
if !okInclude {
211+
continue nextItem
212+
}
213+
}
214+
215+
case "String":
216+
{
217+
//case of string and at least one must match (or) apply (name, template, status, os)
218+
okString := strings.Contains(strings.ToLower(itemValue.Str), strings.ToLower(search))
219+
if okString {
220+
isMatch = true
221+
break
222+
}
223+
if index == len(matches)-1 && !isMatch {
224+
continue nextItem
225+
}
226+
}
227+
case "Null":
228+
{
229+
if strings.ToLower(search) == "null" {
230+
break
231+
}
232+
continue nextItem
233+
}
234+
default:
235+
continue nextItem
236+
}
237+
}
238+
}
239+
}
240+
valueJson := map[string]interface{}{}
241+
err := json.Unmarshal([]byte(item.Raw), &valueJson)
242+
if err != nil {
243+
log.Println("error creating json of item: ", err.Error())
244+
} else {
245+
filteredJson = append(filteredJson, valueJson)
246+
}
247+
}
248+
}
249+
250+
returnJson := map[string]interface{}{}
251+
json.Unmarshal(bodyBytes, &returnJson)
252+
returnJson["totalItems"] = len(items)
253+
if isFilters {
254+
returnJson["items"] = filteredJson
255+
}
256+
257+
return returnJson
258+
}

0 commit comments

Comments
 (0)