support fieldselector filter query secrets (#5300)
* support fieldselector filter query secrets Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com> * support fieldselector filter query secrets Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com> * support fieldselector filter query secrets Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com> * support fieldselector filter query secrets Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com> * support fieldselector filter query secrets Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com> Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
This commit is contained in:
1
go.mod
1
go.mod
@@ -217,6 +217,7 @@ require (
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/nxadm/tail v1.4.4 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v0.1.1 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -638,6 +638,8 @@ github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DV
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
|
||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
|
||||
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
|
||||
@@ -88,6 +88,7 @@ func AddToContainer(c *restful.Container, informerFactory informers.InformerFact
|
||||
Param(webservice.QueryParameter(query.ParameterLimit, "limit").Required(false)).
|
||||
Param(webservice.QueryParameter(query.ParameterAscending, "sort parameters, e.g. reverse=true").Required(false).DefaultValue("ascending=false")).
|
||||
Param(webservice.QueryParameter(query.ParameterOrderBy, "sort parameters, e.g. orderBy=createTime")).
|
||||
Param(webservice.QueryParameter(query.ParameterFieldSelector, "field selector used for filtering, you can use the = , == and != operators with field selectors( = and == mean the same thing), e.g. fieldSelector=type=kubernetes.io/dockerconfigjson, multiple separated by comma").Required(false)).
|
||||
Returns(http.StatusOK, ok, api.ListResult{}))
|
||||
|
||||
webservice.Route(webservice.GET("/namespaces/{namespace}/{resources}/{name}").
|
||||
|
||||
@@ -17,13 +17,20 @@ limitations under the License.
|
||||
package secret
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/klog"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
|
||||
|
||||
"github.com/oliveagle/jsonpath"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
@@ -74,5 +81,55 @@ func (s *secretSearcher) filter(object runtime.Object, filter query.Filter) bool
|
||||
return false
|
||||
}
|
||||
|
||||
if filter.Field == query.ParameterFieldSelector {
|
||||
return contains(secret, filter.Value)
|
||||
}
|
||||
|
||||
return v1alpha3.DefaultObjectMetaFilter(secret.ObjectMeta, filter)
|
||||
}
|
||||
|
||||
// implement a generic query filter to support multiple field selectors with "jsonpath.JsonPathLookup"
|
||||
// https://github.com/oliveagle/jsonpath/blob/master/readme.md
|
||||
func contains(secret *v1.Secret, queryValue query.Value) bool {
|
||||
// call the ParseSelector function of "k8s.io/apimachinery/pkg/fields/selector.go" to validate and parse the selector
|
||||
fieldSelector, err := fields.ParseSelector(string(queryValue))
|
||||
if err != nil {
|
||||
klog.V(4).Infof("failed parse selector error: %s", err)
|
||||
return false
|
||||
}
|
||||
for _, requirement := range fieldSelector.Requirements() {
|
||||
var negative bool
|
||||
// supports '=', '==' and '!='.(e.g. ?fieldSelector=key1=value1,key2=value2)
|
||||
switch requirement.Operator {
|
||||
case selection.NotEquals:
|
||||
negative = true
|
||||
case selection.DoubleEquals:
|
||||
case selection.Equals:
|
||||
negative = false
|
||||
}
|
||||
key := requirement.Field
|
||||
value := requirement.Value
|
||||
|
||||
var input map[string]interface{}
|
||||
data, err := json.Marshal(secret)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("failed marshal to JSON string: %s", err)
|
||||
return false
|
||||
}
|
||||
if err = json.Unmarshal(data, &input); err != nil {
|
||||
klog.V(4).Infof("failed unmarshal to map object: %s", err)
|
||||
return false
|
||||
}
|
||||
rawValue, err := jsonpath.JsonPathLookup(input, "$."+key)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("failed to lookup jsonpath: %s", err)
|
||||
return false
|
||||
}
|
||||
if (negative && fmt.Sprintf("%v", rawValue) != value) || (!negative && fmt.Sprintf("%v", rawValue) == value) {
|
||||
continue
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
26
vendor/github.com/oliveagle/jsonpath/.gitignore
generated
vendored
Normal file
26
vendor/github.com/oliveagle/jsonpath/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.sw[op]
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
.idea
|
||||
8
vendor/github.com/oliveagle/jsonpath/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/oliveagle/jsonpath/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.5
|
||||
- 1.5.1
|
||||
- 1.6.2
|
||||
|
||||
os: linux
|
||||
22
vendor/github.com/oliveagle/jsonpath/LICENSE
generated
vendored
Normal file
22
vendor/github.com/oliveagle/jsonpath/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 oliver
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
722
vendor/github.com/oliveagle/jsonpath/jsonpath.go
generated
vendored
Normal file
722
vendor/github.com/oliveagle/jsonpath/jsonpath.go
generated
vendored
Normal file
@@ -0,0 +1,722 @@
|
||||
package jsonpath
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrGetFromNullObj = errors.New("get attribute from null object")
|
||||
|
||||
func JsonPathLookup(obj interface{}, jpath string) (interface{}, error) {
|
||||
c, err := Compile(jpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Lookup(obj)
|
||||
}
|
||||
|
||||
type Compiled struct {
|
||||
path string
|
||||
steps []step
|
||||
}
|
||||
|
||||
type step struct {
|
||||
op string
|
||||
key string
|
||||
args interface{}
|
||||
}
|
||||
|
||||
func MustCompile(jpath string) *Compiled {
|
||||
c, err := Compile(jpath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func Compile(jpath string) (*Compiled, error) {
|
||||
tokens, err := tokenize(jpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tokens[0] != "@" && tokens[0] != "$" {
|
||||
return nil, fmt.Errorf("$ or @ should in front of path")
|
||||
}
|
||||
tokens = tokens[1:]
|
||||
res := Compiled{
|
||||
path: jpath,
|
||||
steps: make([]step, len(tokens)),
|
||||
}
|
||||
for i, token := range tokens {
|
||||
op, key, args, err := parse_token(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.steps[i] = step{op, key, args}
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (c *Compiled) String() string {
|
||||
return fmt.Sprintf("Compiled lookup: %s", c.path)
|
||||
}
|
||||
|
||||
func (c *Compiled) Lookup(obj interface{}) (interface{}, error) {
|
||||
var err error
|
||||
for _, s := range c.steps {
|
||||
// "key", "idx"
|
||||
switch s.op {
|
||||
case "key":
|
||||
obj, err = get_key(obj, s.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "idx":
|
||||
if len(s.key) > 0 {
|
||||
// no key `$[0].test`
|
||||
obj, err = get_key(obj, s.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.args.([]int)) > 1 {
|
||||
res := []interface{}{}
|
||||
for _, x := range s.args.([]int) {
|
||||
//fmt.Println("idx ---- ", x)
|
||||
tmp, err := get_idx(obj, x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, tmp)
|
||||
}
|
||||
obj = res
|
||||
} else if len(s.args.([]int)) == 1 {
|
||||
//fmt.Println("idx ----------------3")
|
||||
obj, err = get_idx(obj, s.args.([]int)[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
//fmt.Println("idx ----------------4")
|
||||
return nil, fmt.Errorf("cannot index on empty slice")
|
||||
}
|
||||
case "range":
|
||||
if len(s.key) > 0 {
|
||||
// no key `$[:1].test`
|
||||
obj, err = get_key(obj, s.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if argsv, ok := s.args.([2]interface{}); ok == true {
|
||||
obj, err = get_range(obj, argsv[0], argsv[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("range args length should be 2")
|
||||
}
|
||||
case "filter":
|
||||
obj, err = get_key(obj, s.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj, err = get_filtered(obj, obj, s.args.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("expression don't support in filter")
|
||||
}
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func tokenize(query string) ([]string, error) {
|
||||
tokens := []string{}
|
||||
// token_start := false
|
||||
// token_end := false
|
||||
token := ""
|
||||
|
||||
// fmt.Println("-------------------------------------------------- start")
|
||||
for idx, x := range query {
|
||||
token += string(x)
|
||||
// //fmt.Printf("idx: %d, x: %s, token: %s, tokens: %v\n", idx, string(x), token, tokens)
|
||||
if idx == 0 {
|
||||
if token == "$" || token == "@" {
|
||||
tokens = append(tokens, token[:])
|
||||
token = ""
|
||||
continue
|
||||
} else {
|
||||
return nil, fmt.Errorf("should start with '$'")
|
||||
}
|
||||
}
|
||||
if token == "." {
|
||||
continue
|
||||
} else if token == ".." {
|
||||
if tokens[len(tokens)-1] != "*" {
|
||||
tokens = append(tokens, "*")
|
||||
}
|
||||
token = "."
|
||||
continue
|
||||
} else {
|
||||
// fmt.Println("else: ", string(x), token)
|
||||
if strings.Contains(token, "[") {
|
||||
// fmt.Println(" contains [ ")
|
||||
if x == ']' && !strings.HasSuffix(token, "\\]") {
|
||||
if token[0] == '.' {
|
||||
tokens = append(tokens, token[1:])
|
||||
} else {
|
||||
tokens = append(tokens, token[:])
|
||||
}
|
||||
token = ""
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// fmt.Println(" doesn't contains [ ")
|
||||
if x == '.' {
|
||||
if token[0] == '.' {
|
||||
tokens = append(tokens, token[1:len(token)-1])
|
||||
} else {
|
||||
tokens = append(tokens, token[:len(token)-1])
|
||||
}
|
||||
token = "."
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(token) > 0 {
|
||||
if token[0] == '.' {
|
||||
token = token[1:]
|
||||
if token != "*" {
|
||||
tokens = append(tokens, token[:])
|
||||
} else if tokens[len(tokens)-1] != "*" {
|
||||
tokens = append(tokens, token[:])
|
||||
}
|
||||
} else {
|
||||
if token != "*" {
|
||||
tokens = append(tokens, token[:])
|
||||
} else if tokens[len(tokens)-1] != "*" {
|
||||
tokens = append(tokens, token[:])
|
||||
}
|
||||
}
|
||||
}
|
||||
// fmt.Println("finished tokens: ", tokens)
|
||||
// fmt.Println("================================================= done ")
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
/*
|
||||
op: "root", "key", "idx", "range", "filter", "scan"
|
||||
*/
|
||||
func parse_token(token string) (op string, key string, args interface{}, err error) {
|
||||
if token == "$" {
|
||||
return "root", "$", nil, nil
|
||||
}
|
||||
if token == "*" {
|
||||
return "scan", "*", nil, nil
|
||||
}
|
||||
|
||||
bracket_idx := strings.Index(token, "[")
|
||||
if bracket_idx < 0 {
|
||||
return "key", token, nil, nil
|
||||
} else {
|
||||
key = token[:bracket_idx]
|
||||
tail := token[bracket_idx:]
|
||||
if len(tail) < 3 {
|
||||
err = fmt.Errorf("len(tail) should >=3, %v", tail)
|
||||
return
|
||||
}
|
||||
tail = tail[1 : len(tail)-1]
|
||||
|
||||
//fmt.Println(key, tail)
|
||||
if strings.Contains(tail, "?") {
|
||||
// filter -------------------------------------------------
|
||||
op = "filter"
|
||||
if strings.HasPrefix(tail, "?(") && strings.HasSuffix(tail, ")") {
|
||||
args = strings.Trim(tail[2:len(tail)-1], " ")
|
||||
}
|
||||
return
|
||||
} else if strings.Contains(tail, ":") {
|
||||
// range ----------------------------------------------
|
||||
op = "range"
|
||||
tails := strings.Split(tail, ":")
|
||||
if len(tails) != 2 {
|
||||
err = fmt.Errorf("only support one range(from, to): %v", tails)
|
||||
return
|
||||
}
|
||||
var frm interface{}
|
||||
var to interface{}
|
||||
if frm, err = strconv.Atoi(strings.Trim(tails[0], " ")); err != nil {
|
||||
if strings.Trim(tails[0], " ") == "" {
|
||||
err = nil
|
||||
}
|
||||
frm = nil
|
||||
}
|
||||
if to, err = strconv.Atoi(strings.Trim(tails[1], " ")); err != nil {
|
||||
if strings.Trim(tails[1], " ") == "" {
|
||||
err = nil
|
||||
}
|
||||
to = nil
|
||||
}
|
||||
args = [2]interface{}{frm, to}
|
||||
return
|
||||
} else if tail == "*" {
|
||||
op = "range"
|
||||
args = [2]interface{}{nil, nil}
|
||||
return
|
||||
} else {
|
||||
// idx ------------------------------------------------
|
||||
op = "idx"
|
||||
res := []int{}
|
||||
for _, x := range strings.Split(tail, ",") {
|
||||
if i, err := strconv.Atoi(strings.Trim(x, " ")); err == nil {
|
||||
res = append(res, i)
|
||||
} else {
|
||||
return "", "", nil, err
|
||||
}
|
||||
}
|
||||
args = res
|
||||
}
|
||||
}
|
||||
return op, key, args, nil
|
||||
}
|
||||
|
||||
func filter_get_from_explicit_path(obj interface{}, path string) (interface{}, error) {
|
||||
steps, err := tokenize(path)
|
||||
//fmt.Println("f: steps: ", steps, err)
|
||||
//fmt.Println(path, steps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if steps[0] != "@" && steps[0] != "$" {
|
||||
return nil, fmt.Errorf("$ or @ should in front of path")
|
||||
}
|
||||
steps = steps[1:]
|
||||
xobj := obj
|
||||
//fmt.Println("f: xobj", xobj)
|
||||
for _, s := range steps {
|
||||
op, key, args, err := parse_token(s)
|
||||
// "key", "idx"
|
||||
switch op {
|
||||
case "key":
|
||||
xobj, err = get_key(xobj, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "idx":
|
||||
if len(args.([]int)) != 1 {
|
||||
return nil, fmt.Errorf("don't support multiple index in filter")
|
||||
}
|
||||
xobj, err = get_key(xobj, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xobj, err = get_idx(xobj, args.([]int)[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("expression don't support in filter")
|
||||
}
|
||||
}
|
||||
return xobj, nil
|
||||
}
|
||||
|
||||
func get_key(obj interface{}, key string) (interface{}, error) {
|
||||
if reflect.TypeOf(obj) == nil {
|
||||
return nil, ErrGetFromNullObj
|
||||
}
|
||||
switch reflect.TypeOf(obj).Kind() {
|
||||
case reflect.Map:
|
||||
// if obj came from stdlib json, its highly likely to be a map[string]interface{}
|
||||
// in which case we can save having to iterate the map keys to work out if the
|
||||
// key exists
|
||||
if jsonMap, ok := obj.(map[string]interface{}); ok {
|
||||
val, exists := jsonMap[key]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("key error: %s not found in object", key)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
for _, kv := range reflect.ValueOf(obj).MapKeys() {
|
||||
//fmt.Println(kv.String())
|
||||
if kv.String() == key {
|
||||
return reflect.ValueOf(obj).MapIndex(kv).Interface(), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("key error: %s not found in object", key)
|
||||
case reflect.Slice:
|
||||
// slice we should get from all objects in it.
|
||||
res := []interface{}{}
|
||||
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
|
||||
tmp, _ := get_idx(obj, i)
|
||||
if v, err := get_key(tmp, key); err == nil {
|
||||
res = append(res, v)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("object is not map")
|
||||
}
|
||||
}
|
||||
|
||||
func get_idx(obj interface{}, idx int) (interface{}, error) {
|
||||
switch reflect.TypeOf(obj).Kind() {
|
||||
case reflect.Slice:
|
||||
length := reflect.ValueOf(obj).Len()
|
||||
if idx >= 0 {
|
||||
if idx >= length {
|
||||
return nil, fmt.Errorf("index out of range: len: %v, idx: %v", length, idx)
|
||||
}
|
||||
return reflect.ValueOf(obj).Index(idx).Interface(), nil
|
||||
} else {
|
||||
// < 0
|
||||
_idx := length + idx
|
||||
if _idx < 0 {
|
||||
return nil, fmt.Errorf("index out of range: len: %v, idx: %v", length, idx)
|
||||
}
|
||||
return reflect.ValueOf(obj).Index(_idx).Interface(), nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("object is not Slice")
|
||||
}
|
||||
}
|
||||
|
||||
func get_range(obj, frm, to interface{}) (interface{}, error) {
|
||||
switch reflect.TypeOf(obj).Kind() {
|
||||
case reflect.Slice:
|
||||
length := reflect.ValueOf(obj).Len()
|
||||
_frm := 0
|
||||
_to := length
|
||||
if frm == nil {
|
||||
frm = 0
|
||||
}
|
||||
if to == nil {
|
||||
to = length - 1
|
||||
}
|
||||
if fv, ok := frm.(int); ok == true {
|
||||
if fv < 0 {
|
||||
_frm = length + fv
|
||||
} else {
|
||||
_frm = fv
|
||||
}
|
||||
}
|
||||
if tv, ok := to.(int); ok == true {
|
||||
if tv < 0 {
|
||||
_to = length + tv + 1
|
||||
} else {
|
||||
_to = tv + 1
|
||||
}
|
||||
}
|
||||
if _frm < 0 || _frm >= length {
|
||||
return nil, fmt.Errorf("index [from] out of range: len: %v, from: %v", length, frm)
|
||||
}
|
||||
if _to < 0 || _to > length {
|
||||
return nil, fmt.Errorf("index [to] out of range: len: %v, to: %v", length, to)
|
||||
}
|
||||
//fmt.Println("_frm, _to: ", _frm, _to)
|
||||
res_v := reflect.ValueOf(obj).Slice(_frm, _to)
|
||||
return res_v.Interface(), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("object is not Slice")
|
||||
}
|
||||
}
|
||||
|
||||
func regFilterCompile(rule string) (*regexp.Regexp, error) {
|
||||
runes := []rune(rule)
|
||||
if len(runes) <= 2 {
|
||||
return nil, errors.New("empty rule")
|
||||
}
|
||||
|
||||
if runes[0] != '/' || runes[len(runes)-1] != '/' {
|
||||
return nil, errors.New("invalid syntax. should be in `/pattern/` form")
|
||||
}
|
||||
runes = runes[1 : len(runes)-1]
|
||||
return regexp.Compile(string(runes))
|
||||
}
|
||||
|
||||
func get_filtered(obj, root interface{}, filter string) ([]interface{}, error) {
|
||||
lp, op, rp, err := parse_filter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := []interface{}{}
|
||||
|
||||
switch reflect.TypeOf(obj).Kind() {
|
||||
case reflect.Slice:
|
||||
if op == "=~" {
|
||||
// regexp
|
||||
pat, err := regFilterCompile(rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
|
||||
tmp := reflect.ValueOf(obj).Index(i).Interface()
|
||||
ok, err := eval_reg_filter(tmp, root, lp, pat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok == true {
|
||||
res = append(res, tmp)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
|
||||
tmp := reflect.ValueOf(obj).Index(i).Interface()
|
||||
ok, err := eval_filter(tmp, root, lp, op, rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok == true {
|
||||
res = append(res, tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
case reflect.Map:
|
||||
if op == "=~" {
|
||||
// regexp
|
||||
pat, err := regFilterCompile(rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, kv := range reflect.ValueOf(obj).MapKeys() {
|
||||
tmp := reflect.ValueOf(obj).MapIndex(kv).Interface()
|
||||
ok, err := eval_reg_filter(tmp, root, lp, pat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok == true {
|
||||
res = append(res, tmp)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, kv := range reflect.ValueOf(obj).MapKeys() {
|
||||
tmp := reflect.ValueOf(obj).MapIndex(kv).Interface()
|
||||
ok, err := eval_filter(tmp, root, lp, op, rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok == true {
|
||||
res = append(res, tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("don't support filter on this type: %v", reflect.TypeOf(obj).Kind())
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// @.isbn => @.isbn, exists, nil
|
||||
// @.price < 10 => @.price, <, 10
|
||||
// @.price <= $.expensive => @.price, <=, $.expensive
|
||||
// @.author =~ /.*REES/i => @.author, match, /.*REES/i
|
||||
|
||||
func parse_filter(filter string) (lp string, op string, rp string, err error) {
|
||||
tmp := ""
|
||||
|
||||
stage := 0
|
||||
str_embrace := false
|
||||
for idx, c := range filter {
|
||||
switch c {
|
||||
case '\'':
|
||||
if str_embrace == false {
|
||||
str_embrace = true
|
||||
} else {
|
||||
switch stage {
|
||||
case 0:
|
||||
lp = tmp
|
||||
case 1:
|
||||
op = tmp
|
||||
case 2:
|
||||
rp = tmp
|
||||
}
|
||||
tmp = ""
|
||||
}
|
||||
case ' ':
|
||||
if str_embrace == true {
|
||||
tmp += string(c)
|
||||
continue
|
||||
}
|
||||
switch stage {
|
||||
case 0:
|
||||
lp = tmp
|
||||
case 1:
|
||||
op = tmp
|
||||
case 2:
|
||||
rp = tmp
|
||||
}
|
||||
tmp = ""
|
||||
|
||||
stage += 1
|
||||
if stage > 2 {
|
||||
return "", "", "", errors.New(fmt.Sprintf("invalid char at %d: `%c`", idx, c))
|
||||
}
|
||||
default:
|
||||
tmp += string(c)
|
||||
}
|
||||
}
|
||||
if tmp != "" {
|
||||
switch stage {
|
||||
case 0:
|
||||
lp = tmp
|
||||
op = "exists"
|
||||
case 1:
|
||||
op = tmp
|
||||
case 2:
|
||||
rp = tmp
|
||||
}
|
||||
tmp = ""
|
||||
}
|
||||
return lp, op, rp, err
|
||||
}
|
||||
|
||||
func parse_filter_v1(filter string) (lp string, op string, rp string, err error) {
|
||||
tmp := ""
|
||||
istoken := false
|
||||
for _, c := range filter {
|
||||
if istoken == false && c != ' ' {
|
||||
istoken = true
|
||||
}
|
||||
if istoken == true && c == ' ' {
|
||||
istoken = false
|
||||
}
|
||||
if istoken == true {
|
||||
tmp += string(c)
|
||||
}
|
||||
if istoken == false && tmp != "" {
|
||||
if lp == "" {
|
||||
lp = tmp[:]
|
||||
tmp = ""
|
||||
} else if op == "" {
|
||||
op = tmp[:]
|
||||
tmp = ""
|
||||
} else if rp == "" {
|
||||
rp = tmp[:]
|
||||
tmp = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
if tmp != "" && lp == "" && op == "" && rp == "" {
|
||||
lp = tmp[:]
|
||||
op = "exists"
|
||||
rp = ""
|
||||
err = nil
|
||||
return
|
||||
} else if tmp != "" && rp == "" {
|
||||
rp = tmp[:]
|
||||
tmp = ""
|
||||
}
|
||||
return lp, op, rp, err
|
||||
}
|
||||
|
||||
func eval_reg_filter(obj, root interface{}, lp string, pat *regexp.Regexp) (res bool, err error) {
|
||||
if pat == nil {
|
||||
return false, errors.New("nil pat")
|
||||
}
|
||||
lp_v, err := get_lp_v(obj, root, lp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch v := lp_v.(type) {
|
||||
case string:
|
||||
return pat.MatchString(v), nil
|
||||
default:
|
||||
return false, errors.New("only string can match with regular expression")
|
||||
}
|
||||
}
|
||||
|
||||
func get_lp_v(obj, root interface{}, lp string) (interface{}, error) {
|
||||
var lp_v interface{}
|
||||
if strings.HasPrefix(lp, "@.") {
|
||||
return filter_get_from_explicit_path(obj, lp)
|
||||
} else if strings.HasPrefix(lp, "$.") {
|
||||
return filter_get_from_explicit_path(root, lp)
|
||||
} else {
|
||||
lp_v = lp
|
||||
}
|
||||
return lp_v, nil
|
||||
}
|
||||
|
||||
func eval_filter(obj, root interface{}, lp, op, rp string) (res bool, err error) {
|
||||
lp_v, err := get_lp_v(obj, root, lp)
|
||||
|
||||
if op == "exists" {
|
||||
return lp_v != nil, nil
|
||||
} else if op == "=~" {
|
||||
return false, fmt.Errorf("not implemented yet")
|
||||
} else {
|
||||
var rp_v interface{}
|
||||
if strings.HasPrefix(rp, "@.") {
|
||||
rp_v, err = filter_get_from_explicit_path(obj, rp)
|
||||
} else if strings.HasPrefix(rp, "$.") {
|
||||
rp_v, err = filter_get_from_explicit_path(root, rp)
|
||||
} else {
|
||||
rp_v = rp
|
||||
}
|
||||
//fmt.Printf("lp_v: %v, rp_v: %v\n", lp_v, rp_v)
|
||||
return cmp_any(lp_v, rp_v, op)
|
||||
}
|
||||
}
|
||||
|
||||
func isNumber(o interface{}) bool {
|
||||
switch v := o.(type) {
|
||||
case int, int8, int16, int32, int64:
|
||||
return true
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
return true
|
||||
case float32, float64:
|
||||
return true
|
||||
case string:
|
||||
_, err := strconv.ParseFloat(v, 64)
|
||||
if err == nil {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmp_any(obj1, obj2 interface{}, op string) (bool, error) {
|
||||
switch op {
|
||||
case "<", "<=", "==", ">=", ">":
|
||||
default:
|
||||
return false, fmt.Errorf("op should only be <, <=, ==, >= and >")
|
||||
}
|
||||
|
||||
var exp string
|
||||
if isNumber(obj1) && isNumber(obj2) {
|
||||
exp = fmt.Sprintf(`%v %s %v`, obj1, op, obj2)
|
||||
} else {
|
||||
exp = fmt.Sprintf(`"%v" %s "%v"`, obj1, op, obj2)
|
||||
}
|
||||
//fmt.Println("exp: ", exp)
|
||||
fset := token.NewFileSet()
|
||||
res, err := types.Eval(fset, nil, 0, exp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if res.IsValue() == false || (res.Value.String() != "false" && res.Value.String() != "true") {
|
||||
return false, fmt.Errorf("result should only be true or false")
|
||||
}
|
||||
if res.Value.String() == "true" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
114
vendor/github.com/oliveagle/jsonpath/readme.md
generated
vendored
Normal file
114
vendor/github.com/oliveagle/jsonpath/readme.md
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
JsonPath
|
||||
----------------
|
||||
|
||||

|
||||
|
||||
A golang implementation of JsonPath syntax.
|
||||
follow the majority rules in http://goessner.net/articles/JsonPath/
|
||||
but also with some minor differences.
|
||||
|
||||
this library is till bleeding edge, so use it at your own risk. :D
|
||||
|
||||
**Golang Version Required**: 1.5+
|
||||
|
||||
Get Started
|
||||
------------
|
||||
|
||||
```bash
|
||||
go get github.com/oliveagle/jsonpath
|
||||
```
|
||||
|
||||
example code:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/oliveagle/jsonpath"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
var json_data interface{}
|
||||
json.Unmarshal([]byte(data), &json_data)
|
||||
|
||||
res, err := jsonpath.JsonPathLookup(json_data, "$.expensive")
|
||||
|
||||
//or reuse lookup pattern
|
||||
pat, _ := jsonpath.Compile(`$.store.book[?(@.price < $.expensive)].price`)
|
||||
res, err := pat.Lookup(json_data)
|
||||
```
|
||||
|
||||
Operators
|
||||
--------
|
||||
referenced from github.com/jayway/JsonPath
|
||||
|
||||
| Operator | Supported | Description |
|
||||
| ---- | :---: | ---------- |
|
||||
| $ | Y | The root element to query. This starts all path expressions. |
|
||||
| @ | Y | The current node being processed by a filter predicate. |
|
||||
| * | X | Wildcard. Available anywhere a name or numeric are required. |
|
||||
| .. | X | Deep scan. Available anywhere a name is required. |
|
||||
| .<name> | Y | Dot-notated child |
|
||||
| ['<name>' (, '<name>')] | X | Bracket-notated child or children |
|
||||
| [<number> (, <number>)] | Y | Array index or indexes |
|
||||
| [start:end] | Y | Array slice operator |
|
||||
| [?(<expression>)] | Y | Filter expression. Expression must evaluate to a boolean value. |
|
||||
|
||||
Examples
|
||||
--------
|
||||
given these example data.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"store": {
|
||||
"book": [
|
||||
{
|
||||
"category": "reference",
|
||||
"author": "Nigel Rees",
|
||||
"title": "Sayings of the Century",
|
||||
"price": 8.95
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "Evelyn Waugh",
|
||||
"title": "Sword of Honour",
|
||||
"price": 12.99
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "Herman Melville",
|
||||
"title": "Moby Dick",
|
||||
"isbn": "0-553-21311-3",
|
||||
"price": 8.99
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "J. R. R. Tolkien",
|
||||
"title": "The Lord of the Rings",
|
||||
"isbn": "0-395-19395-8",
|
||||
"price": 22.99
|
||||
}
|
||||
],
|
||||
"bicycle": {
|
||||
"color": "red",
|
||||
"price": 19.95
|
||||
}
|
||||
},
|
||||
"expensive": 10
|
||||
}
|
||||
```
|
||||
example json path syntax.
|
||||
----
|
||||
|
||||
| jsonpath | result|
|
||||
| :--------- | :-------|
|
||||
| $.expensive | 10|
|
||||
| $.store.book[0].price | 8.95|
|
||||
| $.store.book[-1].isbn | "0-395-19395-8"|
|
||||
| $.store.book[0,1].price | [8.95, 12.99] |
|
||||
| $.store.book[0:2].price | [8.95, 12.99, 8.99]|
|
||||
| $.store.book[?(@.isbn)].price | [8.99, 22.99] |
|
||||
| $.store.book[?(@.price > 10)].title | ["Sword of Honour", "The Lord of the Rings"]|
|
||||
| $.store.book[?(@.price < $.expensive)].price | [8.95, 8.99] |
|
||||
| $.store.book[:].price | [8.9.5, 12.99, 8.9.9, 22.99] |
|
||||
| $.store.book[?(@.author =~ /(?i).*REES/)].author | "Nigel Rees" |
|
||||
|
||||
> Note: golang support regular expression flags in form of `(?imsU)pattern`
|
||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -671,6 +671,9 @@ github.com/nxadm/tail/winfile
|
||||
# github.com/oklog/ulid v1.3.1 => github.com/oklog/ulid v1.3.1
|
||||
## explicit
|
||||
github.com/oklog/ulid
|
||||
# github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
|
||||
## explicit
|
||||
github.com/oliveagle/jsonpath
|
||||
# github.com/onsi/ginkgo v1.16.4 => github.com/onsi/ginkgo v1.14.0
|
||||
## explicit; go 1.13
|
||||
github.com/onsi/ginkgo
|
||||
|
||||
Reference in New Issue
Block a user