diff --git a/go.mod b/go.mod index 5b70835d..af0b5805 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,22 @@ module go.aporeto.io/manipulate go 1.20 require ( - go.aporeto.io/elemental v1.122.0 - go.aporeto.io/wsc v1.51.0 + go.aporeto.io/elemental v1.122.1-0.20230127211758-51fb47178716 + go.aporeto.io/wsc v1.51.1-0.20230127211509-870916470c0c ) require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de - github.com/ghodss/yaml v1.0.0 + github.com/asdine/storm v2.1.2+incompatible + github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 github.com/gofrs/uuid v4.4.0+incompatible github.com/golang/mock v1.6.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/go-memdb v1.3.4 github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f + github.com/json-iterator/go v1.1.12 github.com/mitchellh/copystructure v1.2.0 github.com/olekukonko/tablewriter v0.0.5 github.com/opentracing/opentracing-go v1.2.0 @@ -24,20 +26,27 @@ require ( github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.15.0 + github.com/stretchr/testify v1.8.1 go.uber.org/zap v1.24.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.5.0 + gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 k8s.io/helm v2.17.0+incompatible ) require ( + github.com/DataDog/zstd v1.4.5 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Sereal/Sereal v0.0.0-20200820125258-a016b7cda3f3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.14.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect @@ -50,15 +59,20 @@ require ( github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/smartystreets/assertions v1.2.0 // indirect + github.com/smartystreets/assertions v1.13.0 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/ugorji/go/codec v1.2.9 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + go.etcd.io/bbolt v1.3.6 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.6.0 // indirect diff --git a/go.sum b/go.sum index a7f90ea1..e7ec5882 100644 --- a/go.sum +++ b/go.sum @@ -38,14 +38,20 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Sereal/Sereal v0.0.0-20200820125258-a016b7cda3f3 h1:XgiXcABXIRyuLNyKHIk6gICrVXcGooDUxR+XMRr2QDM= +github.com/Sereal/Sereal v0.0.0-20200820125258-a016b7cda3f3/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= +github.com/asdine/storm v2.1.2+incompatible h1:dczuIkyqwY2LrtXPz8ixMrU/OFgZp71kbKTHGrXYt/Q= +github.com/asdine/storm v2.1.2+incompatible/go.mod h1:RarYDc9hq1UPLImuiXK3BIWPJLdIygvV3PsInK0FbVQ= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -65,15 +71,13 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -108,6 +112,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -120,6 +127,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -135,15 +143,15 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -151,8 +159,9 @@ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= -github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -161,19 +170,19 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -187,19 +196,12 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -211,6 +213,11 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= @@ -223,7 +230,6 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= @@ -232,12 +238,12 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/assertions v1.13.0 h1:Dx1kYM01xsSqKPno3aqLnrwac2LetPvN23diwyr69Qs= +github.com/smartystreets/assertions v1.13.0/go.mod h1:wDmR7qL282YbGsPy6H/yAsesrxfxaaSlJazyFLYVFx8= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= @@ -267,22 +273,22 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0= -github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.aporeto.io/elemental v1.122.0 h1:jIq9Qum5AgVlvkQTOiJTF5IuRbq6RVXVjPVr/TFOfGI= -go.aporeto.io/elemental v1.122.0/go.mod h1:dKRrJ+ZLwOE0VIbgfoLvskXKvob3h2eSRJijG9eO+9o= -go.aporeto.io/wsc v1.50.0 h1:qYLIWlc/5Hq0Cc+BL8WEcjXrK0sgYcWTqbzdV2ifDyQ= -go.aporeto.io/wsc v1.50.0/go.mod h1:Vvz4EtpZx3TVGxWmpwkq8ho4ku9dIa+omfS7I5Ul0hg= -go.aporeto.io/wsc v1.51.0 h1:xbbsfRGtUWAqTiT9ubK6TKi2+nABNf656XxnEweynRM= -go.aporeto.io/wsc v1.51.0/go.mod h1:0gL/S3uPkS4f3q7/lXF5Hlt804T0d9EhgOSYJkHUbVo= +go.aporeto.io/elemental v1.122.1-0.20230127211758-51fb47178716 h1:S3Ts+DvkrAxMuVU6iDtkHGdHj15b44juna+AjGzU9TU= +go.aporeto.io/elemental v1.122.1-0.20230127211758-51fb47178716/go.mod h1:dKRrJ+ZLwOE0VIbgfoLvskXKvob3h2eSRJijG9eO+9o= +go.aporeto.io/wsc v1.51.1-0.20230127211509-870916470c0c h1:6Zk4WCtdkYudlKXLrEbEalOwgHKlAd1jXDqOWb6L4jU= +go.aporeto.io/wsc v1.51.1-0.20230127211509-870916470c0c/go.mod h1:0gL/S3uPkS4f3q7/lXF5Hlt804T0d9EhgOSYJkHUbVo= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -305,8 +311,6 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -378,6 +382,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -414,7 +419,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -428,6 +432,7 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -438,15 +443,11 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -460,8 +461,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -546,6 +545,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -609,12 +609,15 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/manipbolt/compiler.go b/manipbolt/compiler.go new file mode 100644 index 00000000..f6d7af27 --- /dev/null +++ b/manipbolt/compiler.go @@ -0,0 +1,101 @@ +package manipbolt + +import ( + "fmt" + + "github.com/asdine/storm/q" + "go.aporeto.io/elemental" + "go.aporeto.io/manipulate" +) + +func compileFilter(f *elemental.Filter) (q.Matcher, error) { + + if len(f.Operators()) == 0 { + return q.And(), nil + } + + matchers := []q.Matcher{} + + for i, operator := range f.Operators() { + + switch operator { + + case elemental.AndOperator: + + k := f.Keys()[i] + values := f.Values()[i] + items := []q.Matcher{} + + switch f.Comparators()[i] { + + case elemental.EqualComparator: + + items = append(items, containsOrEqual(k, values[0])) + + case elemental.MatchComparator: + + subs := []q.Matcher{} + for _, value := range values { + + v, ok := value.(string) + if !ok { + return nil, manipulate.ErrCannotBuildQuery{Err: fmt.Errorf("regex only supports string: %v", value)} + } + + subs = append(subs, regexMatcher(k, v)) + } + + items = append(items, q.Or(subs...)) + + case elemental.ContainComparator: + + subs := []q.Matcher{} + for _, value := range values { + subs = append(subs, containsOrEqual(k, value)) + } + + items = append(items, q.Or(subs...)) + + default: + return nil, manipulate.ErrCannotBuildQuery{Err: fmt.Errorf("invalid comparator: %d", f.Comparators()[i])} + } + + matchers = append(matchers, items...) + + case elemental.AndFilterOperator: + + subs := []q.Matcher{} + for _, sub := range f.AndFilters()[i] { + + matcher, err := compileFilter(sub) + if err != nil { + return nil, err + } + + subs = append(subs, matcher) + } + + matchers = append(matchers, q.And(subs...)) + + case elemental.OrFilterOperator: + + subs := []q.Matcher{} + for _, sub := range f.OrFilters()[i] { + + matcher, err := compileFilter(sub) + if err != nil { + return nil, err + } + + subs = append(subs, matcher) + } + + matchers = append(matchers, q.Or(subs...)) + + default: + return nil, manipulate.ErrCannotBuildQuery{Err: fmt.Errorf("invalid operator: %d", operator)} + } + } + + return q.And(matchers...), nil +} diff --git a/manipbolt/compiler_test.go b/manipbolt/compiler_test.go new file mode 100644 index 00000000..dfde5c99 --- /dev/null +++ b/manipbolt/compiler_test.go @@ -0,0 +1,327 @@ +package manipbolt + +import ( + "reflect" + "testing" + + "github.com/asdine/storm/q" + "go.aporeto.io/elemental" +) + +func Test_compileFilter(t *testing.T) { + type args struct { + f *elemental.Filter + } + tests := []struct { + name string + args args + want q.Matcher + wantErr bool + }{ + { + name: "invalid filter", + args: args{ + elemental.NewFilterComposer().WithKey("pid").NotEquals("5d83e7eedb40280001887565").Done(), + }, + want: nil, + wantErr: true, + }, + { + name: "empty filter", + args: args{ + elemental.NewFilterComposer().Done(), + }, + want: q.And(), + wantErr: false, + }, + { + name: "simple filter", + args: args{ + elemental.NewFilterComposer().WithKey("pid").Equals("5d83e7eedb40280001887565").Done(), + }, + want: q.And( + containsOrEqual("pid", "5d83e7eedb40280001887565"), + ), + wantErr: false, + }, + { + name: "two key filter", + args: args{ + elemental.NewFilterComposer().WithKey("Name").Equals("Dragon").WithKey("Name").Equals("Eragon").Done(), + }, + want: q.And( + containsOrEqual("Name", "Dragon"), + containsOrEqual("Name", "Eragon"), + ), + wantErr: false, + }, + { + name: "simple regex", + args: args{ + elemental.NewFilterComposer(). + WithKey("x").Matches("$abc^", ".*"). + Done(), + }, + want: q.And( + q.Or( + regexMatcher("x", "$abc^"), + regexMatcher("x", ".*"), + ), + ), + wantErr: false, + }, + { + name: "regex with unsupported type", + args: args{ + elemental.NewFilterComposer(). + WithKey("x").Matches("$abc^", ".*", true). + Done(), + }, + want: nil, + wantErr: true, + }, + { + name: "nested and filter", + args: args{ + elemental.NewFilterComposer(). + WithKey("namespace").Equals("coucou"). + And( + elemental.NewFilterComposer(). + WithKey("name").Equals("toto"). + WithKey("surname").Equals("titi"). + Done(), + elemental.NewFilterComposer(). + WithKey("yes").Equals(true). + Done(), + elemental.NewFilterComposer(). + WithKey("num").Equals(2). + Done(), + ).Done(), + }, + want: q.And( + containsOrEqual("namespace", "coucou"), + q.And( + q.And( + containsOrEqual("name", "toto"), + containsOrEqual("surname", "titi"), + ), + q.And( + containsOrEqual("yes", true), + ), + q.And( + containsOrEqual("num", 2), + ), + ), + ), + wantErr: false, + }, + { + name: "nested or filter", + args: args{ + elemental.NewFilterComposer(). + WithKey("namespace").Equals("coucou"). + Or( + elemental.NewFilterComposer(). + WithKey("name").Equals("toto"). + WithKey("surname").Equals("titi"). + Done(), + elemental.NewFilterComposer(). + WithKey("yes").Equals(true). + Done(), + elemental.NewFilterComposer(). + WithKey("num").Equals(2). + Done(), + ).Done(), + }, + want: q.And( + containsOrEqual("namespace", "coucou"), + q.Or( + q.And( + containsOrEqual("name", "toto"), + containsOrEqual("surname", "titi"), + ), + q.And( + containsOrEqual("yes", true), + ), + q.And( + containsOrEqual("num", 2), + ), + ), + ), + wantErr: false, + }, + { + name: "nested and/or filter", + args: args{ + elemental.NewFilterComposer(). + WithKey("namespace").Equals("coucou"). + Or( + elemental.NewFilterComposer(). + WithKey("name").Equals("toto"). + WithKey("surname").Equals("titi"). + Done(), + ).And( + elemental.NewFilterComposer(). + WithKey("yes").Equals(true). + Done(), + elemental.NewFilterComposer(). + WithKey("num").Equals(2). + Done(), + ).Done(), + }, + want: q.And( + containsOrEqual("namespace", "coucou"), + q.Or( + q.And( + containsOrEqual("name", "toto"), + containsOrEqual("surname", "titi"), + ), + ), + q.And( + q.And( + containsOrEqual("yes", true), + ), + q.And( + containsOrEqual("num", 2), + ), + ), + ), + wantErr: false, + }, + { + name: "complex nested filters with error invalid sub filter", + args: args{ + elemental.NewFilterComposer(). + WithKey("namespace").Equals("coucou"). + And( + elemental.NewFilterComposer(). + WithKey("name").Equals("toto"). + WithKey("surname").Equals("titi"). + Done(), + elemental.NewFilterComposer(). + WithKey("color").Equals("blue"). + Or( + elemental.NewFilterComposer(). + WithKey("size").Equals("big"). + Done(), + elemental.NewFilterComposer(). + WithKey("size").Equals("medium"). + Done(), + elemental.NewFilterComposer(). + WithKey("list").NotIn("a", "b", "c"). + Done(), + ). + Done(), + ). + Done(), + }, + want: nil, + wantErr: true, + }, + { + name: "complex nested filters", + args: args{ + elemental.NewFilterComposer(). + WithKey("namespace").Equals("coucou"). + And( + elemental.NewFilterComposer(). + WithKey("name").Equals("toto"). + WithKey("surname").Equals("titi"). + Done(), + elemental.NewFilterComposer(). + WithKey("color").Equals("blue"). + WithKey("prefix").Matches("^nope"). + Or( + elemental.NewFilterComposer(). + WithKey("size").Equals("big"). + Done(), + elemental.NewFilterComposer(). + WithKey("size").Equals("medium"). + Done(), + elemental.NewFilterComposer(). + WithKey("list").Contains("a", "b", "c"). + Done(), + ). + Done(), + ). + Done(), + }, + want: q.And( + containsOrEqual("namespace", "coucou"), + q.And( + q.And( + containsOrEqual("name", "toto"), + containsOrEqual("surname", "titi"), + ), + q.And( + containsOrEqual("color", "blue"), + q.Or( + regexMatcher("prefix", "^nope"), + ), + q.Or( + q.And( + containsOrEqual("size", "big"), + ), + q.And( + containsOrEqual("size", "medium"), + ), + q.And( + q.Or( + containsOrEqual("list", "a"), + containsOrEqual("list", "b"), + containsOrEqual("list", "c"), + ), + ), + ), + ), + ), + ), + wantErr: false, + }, + { + name: "found bug", + args: args{ + elemental.NewFilterComposer().Or( + elemental.NewFilterComposer().Or( + elemental.NewFilterComposer(). + WithKey("namespace").Equals("/"). + WithKey("propagate").Equals(true).Done(), + elemental.NewFilterComposer().WithKey("namespace").Equals("/apomux").Done(), + ).WithKey("normalizedTags").Contains("network=dns").Done(), + ).WithKey("archived").Equals(false).Done(), + }, + want: q.And( + q.Or( + q.And( + q.Or( + q.And( + containsOrEqual("namespace", "/"), + containsOrEqual("propagate", true), + ), + q.And( + containsOrEqual("namespace", "/apomux"), + ), + ), + q.Or( + containsOrEqual("normalizedTags", "network=dns"), + ), + ), + ), + containsOrEqual("archived", false), + ), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := compileFilter(tt.args.f) + if (err != nil) != tt.wantErr { + t.Errorf("compileFilter() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("compileFilter() = %#+v, want %#+v", got, tt.want) + } + }) + } +} diff --git a/manipbolt/customcodecs/dummycodec.go b/manipbolt/customcodecs/dummycodec.go new file mode 100644 index 00000000..27564f16 --- /dev/null +++ b/manipbolt/customcodecs/dummycodec.go @@ -0,0 +1,29 @@ +package customcodecs + +import "github.com/asdine/storm/codec" + +var _ codec.MarshalUnmarshaler = &dummyCodec{} + +type dummyCodec struct { +} + +// NewDummyCodec returns a dummyCodec handle. +func NewDummyCodec() codec.MarshalUnmarshaler { + + return &dummyCodec{} +} + +// Marshal returns empty bytes and nil error. +func (d *dummyCodec) Marshal(v interface{}) ([]byte, error) { + return []byte{}, nil +} + +// Unmarshal returns nil error. +func (d *dummyCodec) Unmarshal(b []byte, v interface{}) error { + return nil +} + +// Name of this codec. +func (d *dummyCodec) Name() string { + return "dummyCodec" +} diff --git a/manipbolt/customcodecs/filesizevalidator.go b/manipbolt/customcodecs/filesizevalidator.go new file mode 100644 index 00000000..32edee65 --- /dev/null +++ b/manipbolt/customcodecs/filesizevalidator.go @@ -0,0 +1,118 @@ +package customcodecs + +import ( + "errors" + "fmt" + "os" + + "github.com/asdine/storm/codec" +) + +var _ codec.MarshalUnmarshaler = &fileSizeValidator{} + +// ErrExceedsSize represents the error when the file size exceeds the given size. +var ErrExceedsSize = errors.New("exceeds max size") + +type fileSizeValidator struct { + path string + size int64 + codec codec.MarshalUnmarshaler +} + +// NewFileSizeValidator wraps the given 'codec' with fileSizeValidator and returns the handle. +// Implements codec.MarshalUnmarshaler interface. +func NewFileSizeValidator(path string, size int64, codec codec.MarshalUnmarshaler) codec.MarshalUnmarshaler { + + return &fileSizeValidator{ + path: path, + size: size, + codec: codec, + } +} + +// Marshal validates if the current file size plus the new object size after encoding +// doesn't exceed the max size. If it exceeds, it returns ErrExceedsSize error. +// If the file is non existent, it returns *os.PathError error. Otherwise, returns +// the encoded data. Note that each call to Marshal will also call os.Stat to get +// the current file size. +func (f *fileSizeValidator) Marshal(v interface{}) ([]byte, error) { + + info, err := os.Stat(f.path) + if err != nil { + return nil, err + } + + data, err := f.codec.Marshal(v) + if err != nil { + return nil, err + } + + sz, err := mmapSize(int64(len(data)) + info.Size()) + if err != nil { + return nil, err + } + + if sz > f.size { + return nil, ErrExceedsSize + } + + return data, nil +} + +// Unmarshal calls the underlying codec Unmarshal. +func (f *fileSizeValidator) Unmarshal(b []byte, v interface{}) error { + return f.codec.Unmarshal(b, v) +} + +// Name of this codec appended with underlying codec name. +func (f *fileSizeValidator) Name() string { + return fmt.Sprintf("fileSizeValidator-%s", f.codec.Name()) +} + +/* +The below function including the constants are extracted from +bbolt package https://github.com/etcd-io/bbolt/blob/master/db.go#L391 +We use these to estimate the size that bbolt internally grows the +database and use the value to validate with our max size and abort +if it exceeds. How bbolt allocates size is explained in detail here +https://github.com/boltdb/bolt/issues/308#issuecomment-74811638 +*/ + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0xFFFFFFFFFFFF // 256TB + +// The largest step that can be taken when remapping the mmap. +const maxMmapStep = 1 << 30 // 1GB + +func mmapSize(sz int64) (int64, error) { + // Double the size from 32KB until 1GB. + for i := uint(15); i <= 30; i++ { + if sz <= 1< maxMapSize { + return 0, fmt.Errorf("mmap too large") + } + + // If larger than 1GB then grow by 1GB at a time. + if remainder := sz % int64(maxMmapStep); remainder > 0 { + sz += int64(maxMmapStep) - remainder + } + + // Ensure that the mmap size is a multiple of the page size. + // This should always be true since we're incrementing in MBs. + pageSize := int64(os.Getpagesize()) + if (sz % pageSize) != 0 { + sz = ((sz / pageSize) + 1) * pageSize + } + + // If we've exceeded the max size then only grow up to the max size. + if sz > maxMapSize { + sz = maxMapSize + } + + return sz, nil +} diff --git a/manipbolt/customcodecs/filesizevalidator_test.go b/manipbolt/customcodecs/filesizevalidator_test.go new file mode 100644 index 00000000..33f5d3b1 --- /dev/null +++ b/manipbolt/customcodecs/filesizevalidator_test.go @@ -0,0 +1,115 @@ +package customcodecs + +import ( + "crypto/rand" + "io" + "os" + "testing" + + "github.com/asdine/storm/codec/json" + "github.com/stretchr/testify/require" + testmodel "go.aporeto.io/elemental/test/model" +) + +func writeRandomData(f *os.File, n int64) error { + + _, err := io.CopyN(f, rand.Reader, n) + return err +} + +func Test_FileSizeValidatorJSON(t *testing.T) { + + f, err := os.CreateTemp("", "") + require.Nil(t, err) + require.FileExists(t, f.Name()) + + defer os.RemoveAll(f.Name()) // nolint: errcheck + + size := int64(65 * 1024) + c := NewFileSizeValidator(f.Name(), size, json.Codec) + require.NotNil(t, c) + + l := &testmodel.List{ + Name: "Centos", + } + + d, err := c.Marshal(l) + require.Nil(t, err) + require.NotNil(t, d) + + l1 := &testmodel.List{} + err = c.Unmarshal(d, l1) + require.Nil(t, err) + require.Equal(t, l, l1) +} + +func Test_FileSizeValidator(t *testing.T) { + + f, err := os.CreateTemp("", "") + require.Nil(t, err) + require.FileExists(t, f.Name()) + + defer os.RemoveAll(f.Name()) // nolint: errcheck + + size := int64(32 * 1024) + c := NewFileSizeValidator(f.Name(), size, NewDummyCodec()) + require.NotNil(t, c) + + name := c.Name() + require.Equal(t, "fileSizeValidator-dummyCodec", name) + + l := &testmodel.List{ + Name: "Centos", + } + + err = writeRandomData(f, 200) + require.Nil(t, err) + + d, err := c.Marshal(l) + require.Nil(t, err) + require.NotNil(t, d) + + err = writeRandomData(f, size+12) + require.Nil(t, err) + + l = &testmodel.List{ + Name: "Centos", + } + + _, err = c.Marshal(l) + require.Equal(t, ErrExceedsSize, err) + + info, err := os.Stat(f.Name()) + require.Nil(t, err) + require.NotNil(t, info) + require.LessOrEqual(t, info.Size(), int64((32*1024)+12+200)) + + c = NewFileSizeValidator("dummyPath", size, json.Codec) + require.NotNil(t, c) + + l = &testmodel.List{ + Name: "Centos", + } + + _, err = c.Marshal(l) + require.NotNil(t, err) +} + +func Test_mmapSize(t *testing.T) { + + sz, err := mmapSize(31 * 1024) + require.Nil(t, err) + require.Equal(t, int64(32*1024), sz) + + sz, err = mmapSize(33 * 1024) + require.Nil(t, err) + require.Equal(t, int64(2*32*1024), sz) + + sz, err = mmapSize(maxMapSize + 1) + require.NotNil(t, err) + require.Equal(t, int64(0), sz) + + sz, err = mmapSize(maxMmapStep + 1) + require.Nil(t, err) + require.Equal(t, int64(2*maxMmapStep), sz) +} diff --git a/manipbolt/customcodecs/taggenerator.go b/manipbolt/customcodecs/taggenerator.go new file mode 100644 index 00000000..be962a0a --- /dev/null +++ b/manipbolt/customcodecs/taggenerator.go @@ -0,0 +1,68 @@ +package customcodecs + +import ( + "crypto/rand" + "encoding/hex" + + "github.com/asdine/storm/codec" + jsoniter "github.com/json-iterator/go" +) + +var _ codec.MarshalUnmarshaler = &tagGenerator{} + +type tagGenerator struct { + api jsoniter.API +} + +// NewRandomJSONTagGenerator generates a random JSON struct tag to find +// in struct. Its useful in cases when you don't want to consider +// struct tags while encoding/decoding. This can be solved in a +// lot of different ways. This is an attempt at generating a random +// key with 16 length and the json-iter will use this key to apply +// struct tag operations which of course will not exist. +func NewRandomJSONTagGenerator() (codec.MarshalUnmarshaler, error) { + + tagKey, err := generateRandomString(16) + if err != nil { + return nil, err + } + + api := jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, + TagKey: tagKey, + }.Froze() + + return &tagGenerator{ + api: api, + }, nil +} + +// Marshal calls the underlying Marshal. +func (t *tagGenerator) Marshal(v interface{}) ([]byte, error) { + return t.api.Marshal(v) +} + +// Unmarshal calls the underlying Unmarshal. +func (t *tagGenerator) Unmarshal(b []byte, v interface{}) (err error) { + return t.api.Unmarshal(b, v) +} + +// Name of this codec. +func (t *tagGenerator) Name() string { + return "randomJSONTagGenerator" +} + +func generateRandomString(n int) (string, error) { + + b := make([]byte, n) + + if _, err := rand.Read(b); err != nil { + return "", err + } + + s := hex.EncodeToString(b) + + return s[:n], nil +} diff --git a/manipbolt/customcodecs/taggenerator_test.go b/manipbolt/customcodecs/taggenerator_test.go new file mode 100644 index 00000000..560f0042 --- /dev/null +++ b/manipbolt/customcodecs/taggenerator_test.go @@ -0,0 +1,75 @@ +package customcodecs + +import ( + "testing" + + "github.com/stretchr/testify/require" + testmodel "go.aporeto.io/elemental/test/model" +) + +func Test_TagGenerator(t *testing.T) { + + tr, err := NewRandomJSONTagGenerator() + require.Nil(t, err) + + l := &testmodel.List{ + Name: "Centos", + Unexposed: "abc", + } + + d, err := tr.Marshal(l) + require.Nil(t, err) + require.NotNil(t, d) + + l1 := &testmodel.List{} + err = tr.Unmarshal(d, l1) + require.Nil(t, err) + require.Equal(t, l, l1) + require.Equal(t, "abc", l1.Unexposed) + + i := 45 + + d, err = tr.Marshal(&i) + require.Nil(t, err) + require.NotNil(t, d) + + var i1 int + err = tr.Unmarshal(d, &i1) + require.Nil(t, err) + require.Equal(t, i, i1) + + name := tr.Name() + require.Equal(t, "randomJSONTagGenerator", name) + + type userJSON struct { + Username string `json:"uname"` + Password string `json:"-"` + Age int `json:"age"` + Company string `json:"company"` + List *testmodel.List `json:"list"` + User *userJSON + } + + nt := &userJSON{} + + nt.Username = "ABC" + nt.Password = "XYZ123" + nt.Age = 23 + nt.Company = "RETO" + nt.List = l1 + + d, err = tr.Marshal(nt) + require.Nil(t, err) + require.NotNil(t, d) + + ot := userJSON{} + + err = tr.Unmarshal(d, ot) + require.NotNil(t, err) + + at := &userJSON{} + + err = tr.Unmarshal(d, at) + require.Nil(t, err) + require.Equal(t, nt.List, at.List) +} diff --git a/manipbolt/manipulator.go b/manipbolt/manipulator.go new file mode 100644 index 00000000..d70be412 --- /dev/null +++ b/manipbolt/manipulator.go @@ -0,0 +1,430 @@ +package manipbolt + +import ( + "context" + "fmt" + "os" + "sync" + + "github.com/asdine/storm" + "github.com/asdine/storm/q" + "go.aporeto.io/elemental" + "go.aporeto.io/manipulate" + "gopkg.in/mgo.v2/bson" +) + +var ( + _ manipulate.Manipulator = &boltManipulator{} + _ manipulate.TransactionalManipulator = &boltManipulator{} + _ manipulate.FlushableManipulator = &boltManipulator{} + _ manipulate.BufferedManipulator = &boltManipulator{} +) + +type txnRegistry map[manipulate.TransactionID]storm.Node + +type boltManipulator struct { + db *storm.DB + txnRegistry txnRegistry + manager elemental.ModelManager + cfg *config + + txnRegistryLock sync.RWMutex + dbLock sync.RWMutex +} + +// New creates a new datastore backed by a boltdb with storm toolkit. +func New(path string, manager elemental.ModelManager, options ...Option) (manipulate.TransactionalManipulator, error) { + + cfg := newConfig() + for _, opt := range options { + opt(cfg) + } + + db, err := storm.Open(path, storm.Codec(cfg.codec)) + if err != nil { + return nil, manipulate.ErrCannotExecuteQuery{Err: err} + } + + return &boltManipulator{ + db: db, + txnRegistry: txnRegistry{}, + manager: manager, + cfg: cfg, + }, nil +} + +// Flush will flush the datastore essentially creating a new one. +// It will wait until all transactions are complete. The caller +// has to ensure no other methods are called on the db +// unless the current Flush operation is complete. +func (b *boltManipulator) Flush(ctx context.Context) error { + + path := b.getDB().Bolt.Path() + + if err := b.getDB().Close(); err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := os.RemoveAll(path); err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + db, err := storm.Open(path, storm.Codec(b.cfg.codec)) + if err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + b.setDB(db) + + return nil +} + +// RetrieveMany retrieves the a list of objects with the given elemental.Identity and put them in the given dest. +func (b *boltManipulator) RetrieveMany(mctx manipulate.Context, dest elemental.Identifiables) error { + + if mctx == nil { + mctx = manipulate.NewContext(context.Background()) + } + + txn, err := b.getDB().Begin(false) + if err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := b.executeWithFilter(txn, getOp, mctx.Filter(), dest); err != nil { + txn.Rollback() // nolint: errcheck + return err + } + + if err := txn.Rollback(); err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + return nil +} + +// Retrieve retrieves one or multiple elemental.Identifiables. +// In order to be retrievable, the elemental.Identifiable needs to have their Identifier correctly set. +func (b *boltManipulator) Retrieve(mctx manipulate.Context, object elemental.Identifiable) error { + + txn, err := b.getDB().Begin(false) + if err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := txn.One("ID", object.Identifier(), object); err != nil { + txn.Rollback() // nolint: errcheck + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := txn.Rollback(); err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + return nil +} + +// Create creates a the given elemental.Identifiable. +func (b *boltManipulator) Create(mctx manipulate.Context, object elemental.Identifiable) error { + + if mctx == nil { + mctx = manipulate.NewContext(context.Background()) + } + + tid := mctx.TransactionID() + txn, err := b.txnForID(tid) + if err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if object.Identifier() == "" { + object.SetIdentifier(bson.NewObjectId().Hex()) + } + + if err := txn.Save(object); err != nil { + txn.Rollback() // nolint: errcheck + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := b.commit(tid, txn); err != nil { + return manipulate.ErrCannotCommit{Err: err} + } + + return nil +} + +// Update updates the given elemental.Identifiable. +// In order to be updatable, the elemental.Identifiable needs to have their Identifier correctly set. +func (b *boltManipulator) Update(mctx manipulate.Context, object elemental.Identifiable) error { + + if mctx == nil { + mctx = manipulate.NewContext(context.Background()) + } + + tid := mctx.TransactionID() + txn, err := b.txnForID(tid) + if err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + obj := b.manager.Identifiable(object.Identity()) + if err := txn.One("ID", object.Identifier(), obj); err != nil { + txn.Rollback() // nolint: errcheck + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := txn.Save(object); err != nil { + txn.Rollback() // nolint: errcheck + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := b.commit(tid, txn); err != nil { + return manipulate.ErrCannotCommit{Err: err} + } + + return nil +} + +// Delete deletes the given elemental.Identifiable. +// In order to be deletable, the elemental.Identifiable needs to have their Identifier correctly set. +func (b *boltManipulator) Delete(mctx manipulate.Context, object elemental.Identifiable) error { + + if mctx == nil { + mctx = manipulate.NewContext(context.Background()) + } + + tid := mctx.TransactionID() + txn, err := b.txnForID(tid) + if err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := txn.DeleteStruct(object); err != nil { + txn.Rollback() // nolint: errcheck + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := b.commit(tid, txn); err != nil { + return manipulate.ErrCannotCommit{Err: err} + } + + return nil +} + +// DeleteMany deletes all objects of with the given identity or +// all the ones matching the filter in the given context. +func (b *boltManipulator) DeleteMany(mctx manipulate.Context, identity elemental.Identity) error { + + if mctx == nil { + mctx = manipulate.NewContext(context.Background()) + } + + tid := mctx.TransactionID() + txn, err := b.txnForID(tid) + if err != nil { + return manipulate.ErrCannotExecuteQuery{Err: err} + } + + if err := b.executeWithFilter(txn, deleteOp, mctx.Filter(), b.manager.Identifiables(identity)); err != nil { + txn.Rollback() // nolint: errcheck + return err + } + + if err := b.commit(tid, txn); err != nil { + return manipulate.ErrCannotCommit{Err: err} + } + + return nil +} + +// Count returns the number of objects with the given identity. +func (b *boltManipulator) Count(mctx manipulate.Context, identity elemental.Identity) (int, error) { + + if mctx == nil { + mctx = manipulate.NewContext(context.Background()) + } + + txn, err := b.getDB().Begin(false) + if err != nil { + return -1, manipulate.ErrCannotExecuteQuery{Err: err} + } + + objs := b.manager.Identifiables(identity) + if err := b.executeWithFilter(txn, countOp, mctx.Filter(), objs); err != nil { + txn.Rollback() // nolint: errcheck + return -1, err + } + + if err := txn.Rollback(); err != nil { + return -1, manipulate.ErrCannotExecuteQuery{Err: err} + } + + return len(objs.List()), nil +} + +// Commit commits the given TransactionID. +func (b *boltManipulator) Commit(id manipulate.TransactionID) error { + + txn := b.registeredTxnWithID(id) + if txn == nil { + return manipulate.ErrTransactionNotFound{Err: fmt.Errorf("cannot find transaction: %s", string(id))} + } + + if err := b.commit("", txn); err != nil { + return manipulate.ErrCannotCommit{Err: err} + } + + b.unregisterTxn(id) + + return nil +} + +// Abort aborts the give TransactionID. It returns true if +// a transaction has been effectively aborted, otherwise it returns false. +func (b *boltManipulator) Abort(id manipulate.TransactionID) bool { + + txn := b.registeredTxnWithID(id) + if txn == nil { + return false + } + + if err := txn.Rollback(); err != nil { + // TODO: log error ? interface doesn't support error reporting. + return false + } + + b.unregisterTxn(id) + + return true +} + +func (b *boltManipulator) executeWithFilter(txn storm.Node, ops operation, f *elemental.Filter, dest elemental.Identifiables) error { + + if dest == nil { + return manipulate.ErrCannotExecuteQuery{Err: fmt.Errorf("destination cannot be nil")} + } + + if f == nil { + return b.executeWithMatcher(txn, ops, q.True(), dest) + } + + if len(f.Operators()) == 0 { + return nil + } + + matcher, err := compileFilter(f) + if err != nil { + return err + } + + return b.executeWithMatcher(txn, ops, matcher, dest) +} + +func (b *boltManipulator) executeWithMatcher(txn storm.Node, ops operation, matcher q.Matcher, dest elemental.Identifiables) error { + + // TODO: the ifshort linter shouldn't complain about this + query := txn.Select(matcher) // nolint: ifshort + if query == nil { + return manipulate.ErrCannotBuildQuery{Err: fmt.Errorf("bad query")} + } + + switch ops { + + case getOp, countOp: + + if err := query.Find(dest); err != nil { + if err == storm.ErrNotFound || err == q.ErrUnknownField { + return nil + } + + return manipulate.ErrCannotBuildQuery{Err: err} + } + + case deleteOp: + + obj := b.manager.Identifiable(dest.Identity()) + if err := query.Delete(obj); err != nil { + if err == storm.ErrNotFound || err == q.ErrUnknownField { + return nil + } + + return manipulate.ErrCannotBuildQuery{Err: err} + } + + default: + return manipulate.ErrCannotBuildQuery{Err: fmt.Errorf("invalid operation: %d", ops)} + } + + return nil +} + +func (b *boltManipulator) txnForID(id manipulate.TransactionID) (storm.Node, error) { + + if id == "" { + return b.getDB().Begin(true) + } + + if txn := b.registeredTxnWithID(id); txn != nil { + return txn, nil + } + + txn, err := b.getDB().Begin(true) + if err != nil { + return nil, err + } + + b.registerTxn(id, txn) + + return txn, nil +} + +func (b *boltManipulator) registerTxn(id manipulate.TransactionID, txn storm.Node) { + + b.txnRegistryLock.Lock() + defer b.txnRegistryLock.Unlock() + b.txnRegistry[id] = txn +} + +func (b *boltManipulator) unregisterTxn(id manipulate.TransactionID) { + + b.txnRegistryLock.Lock() + defer b.txnRegistryLock.Unlock() + delete(b.txnRegistry, id) +} + +func (b *boltManipulator) registeredTxnWithID(id manipulate.TransactionID) storm.Node { + + b.txnRegistryLock.RLock() + defer b.txnRegistryLock.RUnlock() + t := b.txnRegistry[id] + + return t +} + +func (b *boltManipulator) getDB() *storm.DB { + + b.dbLock.RLock() + defer b.dbLock.RUnlock() + + return b.db +} + +func (b *boltManipulator) setDB(db *storm.DB) { + + b.dbLock.Lock() + b.db = db + b.dbLock.Unlock() +} + +func (b *boltManipulator) commit(tid manipulate.TransactionID, txn storm.Node) error { + + if tid != "" { + return nil + } + + if err := txn.Commit(); err != nil { + return txn.Rollback() + } + + return nil +} diff --git a/manipbolt/manipulator_test.go b/manipbolt/manipulator_test.go new file mode 100644 index 00000000..1d44a396 --- /dev/null +++ b/manipbolt/manipulator_test.go @@ -0,0 +1,885 @@ +package manipbolt + +import ( + "context" + "os" + "testing" + + "github.com/asdine/storm" + "github.com/asdine/storm/codec/json" + "github.com/stretchr/testify/require" + "go.aporeto.io/elemental" + testmodel "go.aporeto.io/elemental/test/model" + "go.aporeto.io/manipulate" + "go.aporeto.io/manipulate/manipbolt/customcodecs" +) + +func prepareDB(t *testing.T) (manipulate.TransactionalManipulator, []*testmodel.List) { + + f, err := os.CreateTemp("", "") + require.Nil(t, err) + require.FileExists(t, f.Name()) + + c, err := customcodecs.NewRandomJSONTagGenerator() + require.Nil(t, err) + require.NotNil(t, c) + + m, err := New(f.Name(), testmodel.Manager(), OptionCodec(c)) + require.Nil(t, err) + require.NotNil(t, m) + + return m, create(t, m) +} + +func create(t *testing.T, m manipulate.Manipulator) []*testmodel.List { + + p1 := &testmodel.List{ + Name: "Centos7", + ParentID: "xyz", + Slice: []string{"$name=centos7", "category=centos", "a=b", "c=d"}, + } + + p2 := &testmodel.List{ + Name: "Centos8", + Slice: []string{"$name=centos8", "category=centos", "x=y", "w=z"}, + } + + p3 := &testmodel.List{ + Name: "Rhel7", + Slice: []string{"$name=rhel7", "category=rhel", "a=b", "x=y"}, + } + + p4 := &testmodel.List{ + Name: "rhel8", + Slice: []string{"$name=rhel8", "category=rhel", "a=b", "g=h"}, + } + + err := m.Create(nil, p1) + require.Nil(t, err) + err = m.Create(nil, p2) + require.Nil(t, err) + err = m.Create(nil, p3) + require.Nil(t, err) + err = m.Create(nil, p4) + require.Nil(t, err) + + return []*testmodel.List{p1, p2, p3, p4} +} + +func TestBoltManip_New(t *testing.T) { + + f, err := os.CreateTemp("", "") + require.Nil(t, err) + require.FileExists(t, f.Name()) + + defer os.RemoveAll(f.Name()) // nolint: errcheck + + m, err := New(f.Name(), testmodel.Manager()) + require.Nil(t, err) + require.NotNil(t, m) + + m, err = New("", testmodel.Manager()) + require.NotNil(t, err) + require.Nil(t, m) +} + +func TestBoltManip_Create(t *testing.T) { + + f, err := os.CreateTemp("", "") + require.Nil(t, err) + require.FileExists(t, f.Name()) + + defer os.RemoveAll(f.Name()) // nolint: errcheck + + m, err := New(f.Name(), testmodel.Manager()) + require.Nil(t, err) + require.NotNil(t, m) + + p := &testmodel.List{ + Name: "ubuntu", + Slice: []string{"$names=ubuntu16"}, + } + + err = m.Create(nil, p) + require.Nil(t, err) + require.NotEmpty(t, p.ID) + + p1 := &testmodel.List{ + ID: p.ID, + Name: "not good", + } + + err = m.Retrieve(nil, p1) + require.Nil(t, err) + require.Equal(t, p, p1) +} + +func TestBoltManip_ClosedDB(t *testing.T) { + + f, err := os.CreateTemp("", "") + require.Nil(t, err) + require.FileExists(t, f.Name()) + + defer os.RemoveAll(f.Name()) // nolint: errcheck + + m, err := New(f.Name(), testmodel.Manager()) + require.Nil(t, err) + require.NotNil(t, m) + + ctxn, err := m.(*boltManipulator).getDB().Begin(true) + require.Nil(t, err) + require.NotNil(t, ctxn) + + err = ctxn.Rollback() + require.Nil(t, err) + + atxn, err := m.(*boltManipulator).getDB().Begin(false) + require.Nil(t, err) + require.NotNil(t, atxn) + + err = atxn.Rollback() + require.Nil(t, err) + + err = m.(*boltManipulator).getDB().Close() + require.Nil(t, err) + + tid := manipulate.NewTransactionID() + m.(*boltManipulator).registerTxn(tid, ctxn) + + err = m.Commit(tid) + require.NotNil(t, err) + + txn := m.(*boltManipulator).registeredTxnWithID(tid) + require.NotNil(t, txn) + + tid = manipulate.NewTransactionID() + m.(*boltManipulator).registerTxn(tid, atxn) + + ok := m.Abort(tid) + require.False(t, ok) + + txn = m.(*boltManipulator).registeredTxnWithID(tid) + require.NotNil(t, txn) + + p := &testmodel.List{ + Name: "ubuntu", + Slice: []string{"$names=ubuntu16"}, + } + + err = m.Create(nil, p) + require.NotNil(t, err) + + p = &testmodel.List{ + Name: "ubuntu", + Slice: []string{"$names=ubuntu16"}, + } + + err = m.Update(nil, p) + require.NotNil(t, err) + + p1 := &testmodel.List{ + ID: p.ID, + Name: "not good", + } + + err = m.Retrieve(nil, p1) + require.NotNil(t, err) + + err = m.Delete(nil, p1) + require.NotNil(t, err) + + err = m.DeleteMany(nil, testmodel.ListIdentity) + require.NotNil(t, err) + + lists := &testmodel.ListsList{} + + err = m.RetrieveMany(nil, lists) + require.NotNil(t, err) + + _, err = m.Count(nil, testmodel.ListIdentity) + require.NotNil(t, err) +} + +func TestBoltManip_CreateWithCodec(t *testing.T) { + + f, err := os.CreateTemp("", "") + require.Nil(t, err) + require.FileExists(t, f.Name()) + + defer os.RemoveAll(f.Name()) // nolint: errcheck + + codec := customcodecs.NewFileSizeValidator(f.Name(), 64*1024, json.Codec) + + m, err := New(f.Name(), testmodel.Manager(), OptionCodec(codec)) + require.Nil(t, err) + require.NotNil(t, m) + + p := &testmodel.List{ + Name: "ubuntu", + Slice: []string{"$names=ubuntu16"}, + } + + err = m.Create(nil, p) + require.Nil(t, err) + require.NotEmpty(t, p.ID) + + p1 := &testmodel.List{ + ID: p.ID, + Name: "not good", + } + + err = m.Retrieve(nil, p1) + require.Nil(t, err) + require.Equal(t, p, p1) + + p2 := &testmodel.List{ + Name: "ubuntu", + Slice: []string{"$names=ubuntu18"}, + } + + err = m.Create(nil, p2) + require.NotNil(t, err) + require.IsType(t, manipulate.ErrCannotExecuteQuery{}, err) + require.Contains(t, err.Error(), customcodecs.ErrExceedsSize.Error()) + + info, err := os.Stat(f.Name()) + require.Nil(t, err) + require.LessOrEqual(t, info.Size(), int64(64*1024)) +} + +func TestBoltManip_Count(t *testing.T) { + + m, _ := prepareDB(t) + defer os.RemoveAll(m.(*boltManipulator).getDB().Bolt.Path()) // nolint: errcheck + + n, err := m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + n, err = m.Count(nil, testmodel.UserIdentity) + require.Nil(t, err) + require.Equal(t, 0, n) + + mctx := manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().WithKey("slice").Contains("category=rhel", "a=b").Done(), + ), + ) + + n, err = m.Count(mctx, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 3, n) + + filter := elemental.NewFilterComposer().And( + elemental.NewFilterComposer(). + WithKey("slice").Contains("category=rhel").Done(), + elemental.NewFilterComposer(). + WithKey("slice").Contains("a=b").Done(), + elemental.NewFilterComposer(). + WithKey("slice").Contains("g=h").Done(), + ).Done() + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter(filter), + ) + + n, err = m.Count(mctx, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 1, n) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().WithKey("Name").Exists().Done(), + ), + ) + + _, err = m.Count(mctx, testmodel.ListIdentity) + require.NotNil(t, err) +} + +func TestBoltManip_Update(t *testing.T) { + + m, pus := prepareDB(t) + defer os.RemoveAll(m.(*boltManipulator).getDB().Bolt.Path()) // nolint: errcheck + + n, err := m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + l5 := &testmodel.List{ + ID: pus[2].ID, + Name: "SIBI", + Slice: nil, + } + + err = m.Update(nil, l5) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + p1 := &testmodel.List{ + ID: l5.ID, + Name: "not good", + } + + err = m.Retrieve(nil, p1) + require.Nil(t, err) + require.Equal(t, p1, l5) + + pu := &testmodel.List{} + + err = m.Update(nil, pu) + require.IsType(t, manipulate.ErrCannotExecuteQuery{}, err) + require.Contains(t, err.Error(), storm.ErrNotFound.Error()) + + ns := &testmodel.User{ + ID: "abc", + } + + err = m.Update(nil, ns) + require.IsType(t, manipulate.ErrCannotExecuteQuery{}, err) + require.Contains(t, err.Error(), storm.ErrNotFound.Error()) +} + +func TestBoltManip_DeleteMany(t *testing.T) { + + m, _ := prepareDB(t) + defer os.RemoveAll(m.(*boltManipulator).getDB().Bolt.Path()) // nolint: errcheck + + n, err := m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + mctx := manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().WithKey("Name").Matches("^Cent").Done(), + ), + ) + + err = m.DeleteMany(mctx, testmodel.ListIdentity) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 2, n) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().Done(), + ), + ) + + err = m.DeleteMany(mctx, testmodel.ListIdentity) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 2, n) + + err = m.DeleteMany(nil, testmodel.ListIdentity) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 0, n) + + err = m.DeleteMany(nil, testmodel.UserIdentity) + require.Nil(t, err) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().WithKey("Name").Exists().Done(), + ), + ) + + err = m.DeleteMany(mctx, testmodel.ListIdentity) + require.NotNil(t, err) +} + +func TestBoltManip_Retrieve(t *testing.T) { + + m, pus := prepareDB(t) + defer os.RemoveAll(m.(*boltManipulator).getDB().Bolt.Path()) // nolint: errcheck + + p := &testmodel.List{ + ID: pus[0].ID, + } + + err := m.Retrieve(nil, p) + require.Nil(t, err) + require.Equal(t, pus[0], p) + + p1 := &testmodel.List{ + ID: "BADID", + } + + err = m.Retrieve(nil, p1) + require.IsType(t, manipulate.ErrCannotExecuteQuery{}, err) + require.Contains(t, err.Error(), storm.ErrNotFound.Error()) + + p2 := &testmodel.List{} + + err = m.Retrieve(nil, p2) + require.IsType(t, manipulate.ErrCannotExecuteQuery{}, err) + require.Contains(t, err.Error(), storm.ErrNotFound.Error()) + + ns := &testmodel.User{} + + err = m.Retrieve(nil, ns) + require.IsType(t, manipulate.ErrCannotExecuteQuery{}, err) + require.Contains(t, err.Error(), storm.ErrNotFound.Error()) +} + +func TestBoltManip_Delete(t *testing.T) { + + m, pus := prepareDB(t) + defer os.RemoveAll(m.(*boltManipulator).getDB().Bolt.Path()) // nolint: errcheck + + n, err := m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + p1 := &testmodel.List{ + ID: pus[0].ID, + Name: "not good", + } + + err = m.Retrieve(nil, p1) + require.Nil(t, err) + require.Equal(t, pus[0], p1) + + err = m.Delete(nil, p1) + require.Nil(t, err) + + err = m.Retrieve(nil, p1) + require.IsType(t, manipulate.ErrCannotExecuteQuery{}, err) + require.Contains(t, err.Error(), storm.ErrNotFound.Error()) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 3, n) + + p3 := &testmodel.List{ + ID: pus[1].ID, + Name: "not good", + } + + err = m.Retrieve(nil, p3) + require.Nil(t, err) + require.Equal(t, pus[1], p3) + + ns := &testmodel.User{ + ID: "abc", + } + + err = m.Delete(nil, ns) + require.IsType(t, manipulate.ErrCannotExecuteQuery{}, err) + require.Contains(t, err.Error(), storm.ErrNotFound.Error()) + + pu := &testmodel.List{} + + err = m.Delete(nil, pu) + require.IsType(t, manipulate.ErrCannotExecuteQuery{}, err) + require.Contains(t, err.Error(), storm.ErrNotFound.Error()) +} + +func TestBoltManip_RetrieveMany(t *testing.T) { + + m, apus := prepareDB(t) + defer os.RemoveAll(m.(*boltManipulator).getDB().Bolt.Path()) // nolint: errcheck + + err := m.RetrieveMany(nil, nil) + require.NotNil(t, err) + + pus := testmodel.ListsList{} + + err = m.RetrieveMany(nil, &pus) + require.Nil(t, err) + require.Len(t, pus, 4) + + mctx := manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().WithKey("Name").Equals("Centos7").Done(), + ), + ) + + pus = testmodel.ListsList{} + + err = m.RetrieveMany(mctx, &pus) + require.Nil(t, err) + require.Len(t, pus, 1) + require.Equal(t, apus[0], pus[0]) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().WithKey("parentID").Equals("xyz").Done(), + ), + ) + + pus = testmodel.ListsList{} + + err = m.RetrieveMany(mctx, &pus) + require.Nil(t, err) + require.Len(t, pus, 1) + require.Equal(t, apus[0], pus[0]) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().Done(), + ), + ) + + pus = testmodel.ListsList{} + + err = m.RetrieveMany(mctx, &pus) + require.Nil(t, err) + require.Len(t, pus, 0) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().WithKey("Name").Matches("^Cen").Done(), + ), + ) + + pus = testmodel.ListsList{} + + err = m.RetrieveMany(mctx, &pus) + require.Nil(t, err) + require.Len(t, pus, 2) + require.Contains(t, pus, apus[0]) + require.Contains(t, pus, apus[1]) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter( + elemental.NewFilterComposer().WithKey("slice").Contains("category=rhel", "a=b", "none").Done(), + ), + ) + + pus = testmodel.ListsList{} + + err = m.RetrieveMany(mctx, &pus) + require.Nil(t, err) + require.Len(t, pus, 3) + require.Contains(t, pus, apus[0]) + require.Contains(t, pus, apus[2]) + require.Contains(t, pus, apus[3]) + + filter := elemental.NewFilterComposer().And( + elemental.NewFilterComposer(). + WithKey("slice").Equals("category=rhel").Done(), + elemental.NewFilterComposer(). + WithKey("slice").Equals("a=b").Done(), + elemental.NewFilterComposer(). + WithKey("slice").Equals("g=h").Done(), + ).Done() + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter(filter), + ) + + pus = testmodel.ListsList{} + + err = m.RetrieveMany(mctx, &pus) + require.Nil(t, err) + require.Len(t, pus, 1) + require.Contains(t, pus, apus[3]) + + filter = elemental.NewFilterComposer().Or( + elemental.NewFilterComposer(). + WithKey("slice").Contains("category=rhel").Done(), + elemental.NewFilterComposer(). + WithKey("slice").Contains("a=b").Done(), + elemental.NewFilterComposer(). + WithKey("slice").Contains("g=h").Done(), + ).Done() + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter(filter), + ) + + pus = testmodel.ListsList{} + + err = m.RetrieveMany(mctx, &pus) + require.Nil(t, err) + require.Len(t, pus, 3) + require.Contains(t, pus, apus[0]) + require.Contains(t, pus, apus[2]) + require.Contains(t, pus, apus[3]) + + pus = testmodel.ListsList{} + + err = m.RetrieveMany(nil, &pus) + require.Nil(t, err) + require.Len(t, pus, 4) + require.Contains(t, pus, apus[0]) + require.Contains(t, pus, apus[1]) + require.Contains(t, pus, apus[2]) + require.Contains(t, pus, apus[3]) + + nss := testmodel.ListsList{} + + err = m.RetrieveMany(nil, &nss) + require.Nil(t, err) + + filter = elemental.NewFilterComposer().Or( + elemental.NewFilterComposer(). + WithKey("out").Contains("category=rhel").Done(), + elemental.NewFilterComposer(). + WithKey("slice").Contains("a=b").Done(), + elemental.NewFilterComposer(). + WithKey("slice").Contains("g=h").Done(), + ).Done() + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionFilter(filter), + ) + + pus = testmodel.ListsList{} + + err = m.RetrieveMany(mctx, &pus) + require.Nil(t, err) + require.Len(t, pus, 0) +} + +func TestBoltManip_Flush(t *testing.T) { + + m, _ := prepareDB(t) + defer os.RemoveAll(m.(*boltManipulator).getDB().Bolt.Path()) // nolint: errcheck + + n, err := m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + err = m.(manipulate.FlushableManipulator).Flush(context.Background()) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 0, n) + + err = m.(manipulate.FlushableManipulator).Flush(context.Background()) + require.Nil(t, err) + + _ = create(t, m) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) +} + +func TestBoltManip_Commit(t *testing.T) { + + m, _ := prepareDB(t) + defer os.RemoveAll(m.(*boltManipulator).getDB().Bolt.Path()) // nolint: errcheck + + n, err := m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + p := &testmodel.List{ + Name: "SIBI", + Slice: []string{"a=c", "app=centos"}, + } + + tid := manipulate.NewTransactionID() + mctx := manipulate.NewContext( + context.Background(), + manipulate.ContextOptionTransactionID(tid), + ) + + err = m.Create(mctx, p) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + p.Name = "CENTOS" + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionTransactionID(tid), + ) + + err = m.Create(mctx, p) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + err = m.Commit(tid) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 5, n) + + p1 := &testmodel.List{ + ID: p.ID, + } + + err = m.Retrieve(nil, p1) + require.Nil(t, err) + require.Equal(t, p, p1) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionTransactionID(tid), + ) + + p1.Name = "nginx" + + err = m.Update(mctx, p1) + require.Nil(t, err) + + p2 := &testmodel.List{ + ID: p.ID, + } + + err = m.Retrieve(nil, p2) + require.Nil(t, err) + require.Equal(t, "CENTOS", p2.Name) + + err = m.Commit(tid) + require.Nil(t, err) + + p3 := &testmodel.List{ + ID: p.ID, + } + + err = m.Retrieve(nil, p3) + require.Nil(t, err) + require.Equal(t, "nginx", p3.Name) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 5, n) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionTransactionID(tid), + ) + + err = m.Delete(mctx, p3) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 5, n) + + err = m.Commit(tid) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + mctx = manipulate.NewContext( + context.Background(), + manipulate.ContextOptionTransactionID(tid), + ) + + err = m.DeleteMany(mctx, testmodel.ListIdentity) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + err = m.Commit(tid) + require.Nil(t, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 0, n) +} + +func TestBoltManip_Abort(t *testing.T) { + + m, _ := prepareDB(t) + defer os.RemoveAll(m.(*boltManipulator).getDB().Bolt.Path()) // nolint: errcheck + + tid := manipulate.NewTransactionID() + + ok := m.Abort(tid) + require.False(t, ok) + + n, err := m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + p := &testmodel.List{ + Name: "SIBI", + Slice: []string{"a=c", "app=centos"}, + } + + mctx := manipulate.NewContext( + context.Background(), + manipulate.ContextOptionTransactionID(tid), + ) + + err = m.Create(mctx, p) + require.Nil(t, err) + + txn := m.(*boltManipulator).registeredTxnWithID(tid) + require.NotNil(t, txn) + + ok = m.Abort(tid) + require.True(t, ok) + + txn = m.(*boltManipulator).registeredTxnWithID(tid) + require.Nil(t, txn) + + err = m.Commit(tid) + require.IsType(t, manipulate.ErrTransactionNotFound{}, err) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) +} + +func TestBoltManip_NewOnExistingDB(t *testing.T) { + + m, _ := prepareDB(t) + path := m.(*boltManipulator).getDB().Bolt.Path() + + defer os.RemoveAll(path) // nolint: errcheck + + n, err := m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) + + // Only one instance per db can be created. We close the + // existing DB, since we already have an instance open. + err = m.(*boltManipulator).getDB().Close() + require.Nil(t, err) + + c, err := customcodecs.NewRandomJSONTagGenerator() + require.Nil(t, err) + require.NotNil(t, c) + + m, err = New(path, testmodel.Manager(), OptionCodec(c)) + require.Nil(t, err) + require.NotNil(t, m) + + n, err = m.Count(nil, testmodel.ListIdentity) + require.Nil(t, err) + require.Equal(t, 4, n) +} diff --git a/manipbolt/options.go b/manipbolt/options.go new file mode 100644 index 00000000..c20b911a --- /dev/null +++ b/manipbolt/options.go @@ -0,0 +1,22 @@ +package manipbolt + +import "github.com/asdine/storm/codec" + +// An Option represents a manipbolt.Manipulator option. +type Option func(*config) + +type config struct { + codec codec.MarshalUnmarshaler +} + +func newConfig() *config { + return &config{} +} + +// OptionCodec used to set a custom encoder and decoder. +// The default is storm codec which is JSON. +func OptionCodec(codec codec.MarshalUnmarshaler) Option { + return func(c *config) { + c.codec = codec + } +} diff --git a/manipbolt/options_test.go b/manipbolt/options_test.go new file mode 100644 index 00000000..25ffe259 --- /dev/null +++ b/manipbolt/options_test.go @@ -0,0 +1,18 @@ +package manipbolt + +import ( + "testing" + + "github.com/asdine/storm/codec/json" + "github.com/stretchr/testify/require" +) + +func TestOptions(t *testing.T) { + + c := newConfig() + require.NotNil(t, c) + require.Nil(t, c.codec) + + OptionCodec(json.Codec)(c) + require.NotNil(t, c.codec) +} diff --git a/manipbolt/utils.go b/manipbolt/utils.go new file mode 100644 index 00000000..e5b059a6 --- /dev/null +++ b/manipbolt/utils.go @@ -0,0 +1,124 @@ +package manipbolt + +import ( + "reflect" + "regexp" + "strings" + "sync" + + "github.com/asdine/storm/q" +) + +type operation int + +const ( + getOp operation = iota + deleteOp + countOp +) + +var _ q.FieldMatcher = &containsOrEqualMatcher{} +var _ q.FieldMatcher = ®expMatcher{} +var _ q.Matcher = &fieldMatcherCaseInsensitive{} + +type containsOrEqualMatcher struct { + field string + value interface{} +} + +func (c *containsOrEqualMatcher) MatchField(v interface{}) (bool, error) { + + ev := reflect.ValueOf(v) + if ev.Kind() != reflect.Slice { + eq, ok := q.Eq(c.field, c.value).(q.FieldMatcher) + if !ok { + return false, nil + } + + return eq.MatchField(v) + } + + for i := 0; i < ev.Len(); i++ { + e := ev.Index(i).Interface() + + if e == c.value { + return true, nil + } + } + + return false, nil +} + +// containsOrEqual implements q.FieldMatcher interface. +// It combines both equal and contains matcher in one. +// Refer corresponding unit tests for details. +func containsOrEqual(field string, v interface{}) q.Matcher { + return newFieldMatcherCaseInsensitive(field, &containsOrEqualMatcher{field, v}) +} + +var regexpCache = struct { + sync.RWMutex + m map[string]*regexp.Regexp +}{m: make(map[string]*regexp.Regexp)} + +type regexpMatcher struct { + r *regexp.Regexp + err error +} + +func (r *regexpMatcher) MatchField(v interface{}) (bool, error) { + + if r.err != nil { + return false, r.err + } + + return r.r.MatchString(v.(string)), nil +} + +// regexMatcher creates a regexp matcher. It checks if the +// given field matches the given regexp. Note that this only +// supports fields of type string. Field is case insensitive. +func regexMatcher(field string, re string) q.Matcher { + + regexpCache.RLock() + if r, ok := regexpCache.m[re]; ok { + regexpCache.RUnlock() + return newFieldMatcherCaseInsensitive(field, ®expMatcher{r, nil}) + } + regexpCache.RUnlock() + + regexpCache.Lock() + r, err := regexp.Compile(re) + if err == nil { + regexpCache.m[re] = r + } + regexpCache.Unlock() + + return newFieldMatcherCaseInsensitive(field, ®expMatcher{r, err}) +} + +type fieldMatcherCaseInsensitive struct { + q.FieldMatcher + field string +} + +func (r fieldMatcherCaseInsensitive) Match(i interface{}) (bool, error) { + + v := reflect.Indirect(reflect.ValueOf(i)) + + field := v.FieldByNameFunc(func(n string) bool { + return strings.EqualFold(n, r.field) + }) + + if !field.IsValid() { + return false, q.ErrUnknownField + } + + return r.MatchField(field.Interface()) +} + +// newFieldMatcherCaseInsensitive creates a case insensitive +// Matcher for a given field. +func newFieldMatcherCaseInsensitive(field string, fm q.FieldMatcher) q.Matcher { + return fieldMatcherCaseInsensitive{fm, field} +} diff --git a/manipbolt/utils_test.go b/manipbolt/utils_test.go new file mode 100644 index 00000000..b0bcf668 --- /dev/null +++ b/manipbolt/utils_test.go @@ -0,0 +1,207 @@ +package manipbolt + +import ( + "errors" + "regexp" + "testing" +) + +func Test_containsOrEqualMatcher_MatchField(t *testing.T) { + type fields struct { + field string + value interface{} + } + type args struct { + v interface{} + } + tests := []struct { + name string + fields fields + args args + want bool + wantErr bool + }{ + { + name: "simple string equal value", + fields: fields{ + "key", + "abc", + }, + args: args{ + "abc", + }, + want: true, + wantErr: false, + }, + { + name: "simple string unequal value", + fields: fields{ + "key", + "abc", + }, + args: args{ + "nope", + }, + want: false, + wantErr: false, + }, + { + name: "simple num equal value", + fields: fields{ + "key", + 23, + }, + args: args{ + 23, + }, + want: true, + wantErr: false, + }, + { + name: "simple num unequal value", + fields: fields{ + "key", + 45, + }, + args: args{ + 60, + }, + want: false, + wantErr: false, + }, + { + name: "string list contains value", + fields: fields{ + "key", + "abc", + }, + args: args{ + []string{"doit", "abc", "yes"}, + }, + want: true, + wantErr: false, + }, + { + name: "string list not contains value", + fields: fields{ + "key", + "nope", + }, + args: args{ + []string{"doit", "abc", "yes"}, + }, + want: false, + wantErr: false, + }, + { + name: "num list contains value", + fields: fields{ + "key", + 24, + }, + args: args{ + []int{56, 75, 24}, + }, + want: true, + wantErr: false, + }, + { + name: "num list not contains value", + fields: fields{ + "key", + 35, + }, + args: args{ + []int{56, 75, 24}, + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &containsOrEqualMatcher{ + field: tt.fields.field, + value: tt.fields.value, + } + got, err := c.MatchField(tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("containsOrEqualMatcher.MatchField() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("containsOrEqualMatcher.MatchField() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_regexpMatcher_MatchField(t *testing.T) { + type fields struct { + r *regexp.Regexp + err error + } + type args struct { + v interface{} + } + tests := []struct { + name string + fields fields + args args + want bool + wantErr bool + }{ + { + name: "error", + fields: fields{ + r: nil, + err: errors.New("failed"), + }, + args: args{ + "abc", + }, + want: false, + wantErr: true, + }, + { + name: "valid", + fields: fields{ + r: regexp.MustCompile("^ab"), + err: nil, + }, + args: args{ + "abcef", + }, + want: true, + wantErr: false, + }, + { + name: "valid non-match", + fields: fields{ + r: regexp.MustCompile("^bca"), + err: nil, + }, + args: args{ + "abcef", + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := ®expMatcher{ + r: tt.fields.r, + err: tt.fields.err, + } + got, err := r.MatchField(tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("regexpMatcher.MatchField() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("regexpMatcher.MatchField() = %v, want %v", got, tt.want) + } + }) + } +}