From 89b1b6db87508994f80e7c7a5cbb7c709babc97f Mon Sep 17 00:00:00 2001 From: nuclearwu Date: Wed, 26 Oct 2022 10:14:49 +0800 Subject: [PATCH] support fieldselector filter query secrets (#5300) * support fieldselector filter query secrets Signed-off-by: wuzhongjian * support fieldselector filter query secrets Signed-off-by: wuzhongjian * support fieldselector filter query secrets Signed-off-by: wuzhongjian * support fieldselector filter query secrets Signed-off-by: wuzhongjian * support fieldselector filter query secrets Signed-off-by: wuzhongjian Signed-off-by: wuzhongjian --- go.mod | 1 + go.sum | 2 + pkg/kapis/resources/v1alpha3/register.go | 1 + .../resources/v1alpha3/secret/secrets.go | 57 ++ .../github.com/oliveagle/jsonpath/.gitignore | 26 + .../github.com/oliveagle/jsonpath/.travis.yml | 8 + vendor/github.com/oliveagle/jsonpath/LICENSE | 22 + .../github.com/oliveagle/jsonpath/jsonpath.go | 722 ++++++++++++++++++ .../github.com/oliveagle/jsonpath/readme.md | 114 +++ vendor/modules.txt | 3 + 10 files changed, 956 insertions(+) create mode 100644 vendor/github.com/oliveagle/jsonpath/.gitignore create mode 100644 vendor/github.com/oliveagle/jsonpath/.travis.yml create mode 100644 vendor/github.com/oliveagle/jsonpath/LICENSE create mode 100644 vendor/github.com/oliveagle/jsonpath/jsonpath.go create mode 100644 vendor/github.com/oliveagle/jsonpath/readme.md diff --git a/go.mod b/go.mod index fba35a56d..f6a997bab 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 60afb8f9a..95872c874 100644 --- a/go.sum +++ b/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= diff --git a/pkg/kapis/resources/v1alpha3/register.go b/pkg/kapis/resources/v1alpha3/register.go index bada3d506..0de95ac14 100644 --- a/pkg/kapis/resources/v1alpha3/register.go +++ b/pkg/kapis/resources/v1alpha3/register.go @@ -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}"). diff --git a/pkg/models/resources/v1alpha3/secret/secrets.go b/pkg/models/resources/v1alpha3/secret/secrets.go index 64295fe6a..fe8e8ff02 100644 --- a/pkg/models/resources/v1alpha3/secret/secrets.go +++ b/pkg/models/resources/v1alpha3/secret/secrets.go @@ -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 +} diff --git a/vendor/github.com/oliveagle/jsonpath/.gitignore b/vendor/github.com/oliveagle/jsonpath/.gitignore new file mode 100644 index 000000000..c469a41f6 --- /dev/null +++ b/vendor/github.com/oliveagle/jsonpath/.gitignore @@ -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 diff --git a/vendor/github.com/oliveagle/jsonpath/.travis.yml b/vendor/github.com/oliveagle/jsonpath/.travis.yml new file mode 100644 index 000000000..f7e5a74d8 --- /dev/null +++ b/vendor/github.com/oliveagle/jsonpath/.travis.yml @@ -0,0 +1,8 @@ +language: go + +go: + - 1.5 + - 1.5.1 + - 1.6.2 + +os: linux diff --git a/vendor/github.com/oliveagle/jsonpath/LICENSE b/vendor/github.com/oliveagle/jsonpath/LICENSE new file mode 100644 index 000000000..530afca3d --- /dev/null +++ b/vendor/github.com/oliveagle/jsonpath/LICENSE @@ -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. + diff --git a/vendor/github.com/oliveagle/jsonpath/jsonpath.go b/vendor/github.com/oliveagle/jsonpath/jsonpath.go new file mode 100644 index 000000000..00dc6fde8 --- /dev/null +++ b/vendor/github.com/oliveagle/jsonpath/jsonpath.go @@ -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 +} diff --git a/vendor/github.com/oliveagle/jsonpath/readme.md b/vendor/github.com/oliveagle/jsonpath/readme.md new file mode 100644 index 000000000..37ee7dcd4 --- /dev/null +++ b/vendor/github.com/oliveagle/jsonpath/readme.md @@ -0,0 +1,114 @@ +JsonPath +---------------- + +![Build Status](https://travis-ci.org/oliveagle/jsonpath.svg?branch=master) + +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. | +| . | Y | Dot-notated child | +| ['' (, '')] | X | Bracket-notated child or children | +| [ (, )] | Y | Array index or indexes | +| [start:end] | Y | Array slice operator | +| [?()] | 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` \ No newline at end of file diff --git a/vendor/modules.txt b/vendor/modules.txt index cce6a7739..304696fb9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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