diff --git a/go.mod b/go.mod index 4d6fe7f..7577029 100644 --- a/go.mod +++ b/go.mod @@ -1,26 +1,27 @@ module github.com/linkinghack/structemplate -go 1.20 +go 1.22.0 require ( github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46 github.com/pkg/errors v0.9.1 - k8s.io/apimachinery v0.25.0 + k8s.io/apimachinery v0.29.2 ) require ( - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.70.1 // indirect - k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index ab16644..7f8bcde 100644 --- a/go.sum +++ b/go.sum @@ -3,37 +3,36 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46 h1:7QPwrLT79GlD5sizHf27aoY2RTvw62mO6x7mxkScNk0= github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46/go.mod h1:esf2rsHFNlZlxsqsZDojNBcnNs5REqIvRrWRHqX0vEU= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -45,8 +44,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -55,8 +54,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -64,25 +63,25 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= -k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= +k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/jsonpath_renderer.go b/jsonpath_renderer.go index 97e8357..f274b42 100644 --- a/jsonpath_renderer.go +++ b/jsonpath_renderer.go @@ -4,7 +4,6 @@ import ( "fmt" "reflect" "strconv" - "strings" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -58,6 +57,7 @@ func RenderJsonPathParamForUnstructuredObj(obj *unstructured.Unstructured, param if err := AppendArrayField(obj, paramPath.ParamJsonPath, value); err != nil { return err } + return nil } // 处理MapKV追加模式 @@ -65,6 +65,7 @@ func RenderJsonPathParamForUnstructuredObj(obj *unstructured.Unstructured, param if err := AppendMapForUnstructuredObj(obj, paramPath.ParamJsonPath, paramDef.MapKey, value); err != nil { return err } + return nil } // 处理一般属性设置模式 @@ -74,46 +75,42 @@ func RenderJsonPathParamForUnstructuredObj(obj *unstructured.Unstructured, param return nil } -func parseKeyPath(keyPath string) []string { - t1 := strings.Trim(keyPath, "$") - t1 = strings.Trim(t1, ".") - return strings.Split(t1, ".") -} - // AppendArrayField 为指定Unstructured对象的数组类型字段增加值 // *将自动判断keyPath指定对象是否为数组类型,或为空时自动创建数组 -// *若keyPath不是 +// *若keyPath位置的值不是数组类型,则抛出错误 func AppendArrayField(obj *unstructured.Unstructured, keyPath string, value interface{}) error { - kp := parseKeyPath(keyPath) - subObj, exists, err := unstructured.NestedFieldNoCopy(obj.Object, kp...) - if err != nil { - return err - } + return SetNestedField(obj.Object, keyPath, value, true) + // kp := parseKeyPath(keyPath) - if !exists { - // 全新字段,创建数组直接设置 - unstructured.SetNestedField(obj.Object, []interface{}{value}, kp...) - } else { - // 检查数据类型为slice - t := reflect.TypeOf(subObj) - switch t.Kind() { - case reflect.Slice, reflect.Array: - subObj, ok := subObj.([]interface{}) - if !ok { - return errors.New("转换目标字段为slice出错:" + keyPath) - } - newSli := append(subObj, value) + // subObj, exists, err := unstructured.NestedFieldNoCopy(obj.Object, kp...) + // if err != nil { + // return err + // } - // 重新替换 - return unstructured.SetNestedSlice(obj.Object, newSli, kp[:len(kp)-1]...) - default: - return errors.New("目标字段不是slice或array") - } - } - return nil + // if !exists { + // // 全新字段,创建数组直接设置 + // unstructured.SetNestedField(obj.Object, []interface{}{value}, kp...) + // } else { + // // 检查数据类型为slice + // t := reflect.TypeOf(subObj) + // switch t.Kind() { + // case reflect.Slice, reflect.Array: + // subObj, ok := subObj.([]interface{}) + // if !ok { + // return errors.New("转换目标字段为slice出错:" + keyPath) + // } + // newSli := append(subObj, value) + + // // 重新替换 + // return SetNestedField(obj.Object, kp[:len(kp)-1], newSli, true) + // default: + // return errors.New("目标字段不是slice或array") + // } + // } + // return nil } -// SetFieldOfSetValueOfUnstructredObjUnstructured 为指定的Unstructured对象在keyPath指定的位置上设置任意值 +// SetValueOfUnstructredObj 为指定的Unstructured对象在keyPath指定的位置上设置任意值 // keyPath: `.spec.name1.name2` 格式的json path表达式 // value: 需要设置的任意值 // @@ -127,11 +124,11 @@ func AppendMapForUnstructuredObj(obj *unstructured.Unstructured, mapParamJsonPat processFunc := func(targetValue interface{}) error { // 完成参数设定 // 解析jsonPath表达式 e.g. `.metadata.namespace` --> []string{"metadata", "namespace"} - jsonPathArr := parseKeyPath(mapParamJsonPathKey) - if len(key) > 0 { - jsonPathArr = append(jsonPathArr, key) - } - return unstructured.SetNestedField(obj.Object, targetValue, jsonPathArr...) + // jsonPathArr := parseKeyPath(mapParamJsonPathKey) + // if len(key) > 0 { + // jsonPathArr = append(jsonPathArr, key) + // } + return SetNestedField(obj.Object, fmt.Sprintf("%s.%s", mapParamJsonPathKey, key), targetValue, false) } safeValue := reflect.ValueOf(value) @@ -148,7 +145,7 @@ func AppendMapForUnstructuredObj(obj *unstructured.Unstructured, mapParamJsonPat return errors.Wrap(err, "整数型参数解析出错") } return processFunc(intV) - case reflect.Float32: + case reflect.Float32, reflect.Float64: return processFunc(value.(float64)) default: return processFunc(value) diff --git a/renderer_test.go b/renderer_test.go new file mode 100644 index 0000000..d5cae53 --- /dev/null +++ b/renderer_test.go @@ -0,0 +1,181 @@ +package structemplate + +import ( + "bytes" + "encoding/json" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/yaml" +) + +var manifest string = ` +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TLSRoute +metadata: + name: test-target-tlsroute + namespace: gateways + labels: + istio: test-target-gateway +spec: + hostnames: + - "hostname1.example.com" + - "hostname2.example.com" + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: example-gateway + namespace: gateways + port: 20022 + rules: + - backendRefs: + - name: test-target + namespace: gateways + port: 6443 +` + +var param = TemplateDynamicParam{ + ParamCode: "TLS_SNI_HOSTS", + ParamName: "TLS SNI hostname", + FunctionScope: "SYSTEM", + ParamType: ParamTypeJsonPath, + ValueInjectTargets: []JsonPathParamTarget{ + { + TargetGVK: schema.GroupVersionKind{Group: "gateway.networking.k8s.io", Version: "v1alpha2", Kind: "TLSRoute"}, + ParamJsonPath: ".spec.hostnames", + }, + }, + Optional: false, + Default: "default.example.com", + ValueDataType: "string", + AppendArray: true, +} + +func TestRenderJsonpathParam_AppendArray(t *testing.T) { + obj := parseObject(t) + unstructuredObj := unstructured.Unstructured{Object: obj} + var newParam = param + + objJson, _ := json.Marshal(obj) + t.Logf("Before append array, the object: %s\n", objJson) + + err := RenderJsonPathParamForUnstructuredObj(&unstructuredObj, &newParam, &newParam.ValueInjectTargets[0], []string{"correct-element1.sni-hostname.example.com", "correct-element2.sni-hostname.example.com"}) + if err != nil { + t.Logf("Failed render JsonPath param: %+v", err) + t.FailNow() + return + } + + err = RenderJsonPathParamForUnstructuredObj(&unstructuredObj, &newParam, &newParam.ValueInjectTargets[0], "correct.sni-hostname.example.com") + if err != nil { + t.Logf("Failed render JsonPath param: %+v", err) + t.FailNow() + return + } + + objJson, _ = json.Marshal(obj) + t.Logf("After append array: %s \n", string(objJson)) + + // check result + hostnames, err := GetValueOfNestedField(unstructuredObj.Object, ".spec.hostnames") + if err != nil { + t.Error(err) + t.FailNow() + return + } + hostnamesArray := hostnames.([]interface{}) + t.Logf("hostnames array: %+v", hostnames) + if len(hostnamesArray) != 5 || hostnamesArray[4] != "correct.sni-hostname.example.com" { + t.Log("Append array test failed") + t.FailNow() + return + } + + // show results + + jsonResult, err := json.Marshal(obj) + if err != nil { + t.Log(err) + t.FailNow() + return + } + t.Logf("After render JsonPath param: %s", jsonResult) +} + +func TestRenderJsonpathParam_ArrayAddObj(t *testing.T) { + obj := parseObject(t) + unstructuredObj := unstructured.Unstructured{Object: obj} + var newParam = param + + newParam.AppendArray = false + newParam.ValueInjectTargets[0].ParamJsonPath = ".spec.hostnames.fakeKey" + err := RenderJsonPathParamForUnstructuredObj(&unstructuredObj, &newParam, &newParam.ValueInjectTargets[0], "fakeValue") + if err == nil { + t.Log("Expected error does not occurred") + t.FailNow() + return + } + // t.Logf("illegal json path error: %s", err.Error()) +} + +func TestRenderJsonpathParam_AppendMap(t *testing.T) { + var newParam = param + newParam.MapKey = "newObjectKey" + newParam.AppendArray = false + newParam.ValueInjectTargets[0].ParamJsonPath = ".spec" + obj := parseObject(t) + unstructuredObj := unstructured.Unstructured{Object: obj} + RenderJsonPathParamForUnstructuredObj(&unstructuredObj, &newParam, &newParam.ValueInjectTargets[0], "newObjectValue") + objJson, _ := json.Marshal(obj) + t.Logf("After append map: %s\n", string(objJson)) +} + +func TestRenderJsonpathParam_NormalSetField(t *testing.T) { + var newParam = param + newParam.AppendArray = false + newParam.ValueInjectTargets[0].ParamJsonPath = ".spec.newEmptyObject.newEmptyField" + obj := parseObject(t) + unstructuredObj := unstructured.Unstructured{Object: obj} + RenderJsonPathParamForUnstructuredObj(&unstructuredObj, &newParam, &newParam.ValueInjectTargets[0], "newObjectValue") + objJson, _ := json.Marshal(obj) + t.Logf("After normal set: %s\n", string(objJson)) +} + +func parseObject(t *testing.T) map[string]interface{} { + yamlReader := bytes.NewReader([]byte(manifest)) + decoder := yaml.NewYAMLOrJSONDecoder(yamlReader, 0) + obj := make(map[string]interface{}) + if err := decoder.Decode(&obj); err != nil { + t.Log(err) + t.FailNow() + return nil + } + return obj +} + +func TestJsonpathGetterSetter(t *testing.T) { + obj := parseObject(t) + SetNestedField(obj, ".spec.parentRefs.[0].name", "JSONHACKED", false) + + v, err := GetValueOfNestedField(obj, ".spec.parentRefs.[0].name") + if err != nil { + t.Logf("Failed get value: %+v", err) + t.FailNow() + return + } + if v != "JSONHACKED" { + t.Logf("Value retrived: %+v", v) + t.FailNow() + return + } + t.Logf("Retrived: %s", v) + + jsonResult, err := json.Marshal(obj) + if err != nil { + t.Log(err) + t.FailNow() + return + } + t.Logf("After modify: %+v", string(jsonResult)) +} diff --git a/unstructured_field.go b/unstructured_field.go new file mode 100644 index 0000000..0b7942c --- /dev/null +++ b/unstructured_field.go @@ -0,0 +1,258 @@ +package structemplate + +import ( + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +func ParseKeyPath(keyPath string) []string { + t1 := strings.Trim(keyPath, "$") + t1 = strings.Trim(t1, ".") + return strings.Split(t1, ".") +} + +// GetValueOfNestedField gets the value of field specified by `jsonPath` from the target object. +func GetValueOfNestedField(object map[string]interface{}, jsonPath string) (interface{}, error) { + if len(jsonPath) < 1 || jsonPath == "." { + return DeepCopyJSONValue(object), nil + } + + paths := ParseKeyPath(jsonPath) + var field interface{} = DeepCopyJSONValue(object) + + var tracedPath string = "" + for i := 0; i < len(paths); i++ { + key := paths[i] + tracedPath += "." + key + switch reflect.TypeOf(field).Kind() { + case reflect.Slice: + idx, err := ParseJsonPathArrayIndex(key) + if err != nil { + return nil, errors.Wrap(err, "cannot parse the index of array field: "+tracedPath) + } + tmpField := field.([]interface{}) + if len(tmpField) <= int(idx) { + return nil, errors.New(fmt.Sprintf("array field index out of bounds: %d of %s", idx, tracedPath)) + } + field = tmpField[idx] + case reflect.Map: + tmpField := field.(map[string]interface{}) + field = tmpField[key] + default: + return nil, errors.New("field does not exist: " + tracedPath) + } + } + return field, nil +} + +// SetNestedField sets a value in the structure of object +// @Param appendArray: the element to operating is an array and the value should be appended in the array +// @Param value: the value to inject. When append array is true, and value is an array, the elements in `value` will all be appended to the template. +func SetNestedField(object map[string]interface{}, jsonPath string, value interface{}, appendArray bool) error { + if jsonPath == "" { + return errors.New("param jsonPath is empty") + } + + paths := ParseKeyPath(jsonPath) + var jumper interface{} = object + var jumperBackNode interface{} = object + + // finding the node before last key + for i := 0; i < len(paths)-1; i++ { + currentKey := paths[i] + if len(currentKey) < 1 { + // ignore empty slots + continue + } + + switch reflect.TypeOf(jumper).Kind() { + case reflect.Slice: + idx, err := ParseJsonPathArrayIndex(currentKey) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("arror parse array index: %s", currentKey)) + } + jumperHolder := jumper.([]interface{}) + if int(idx) >= len(jumperHolder) { + return fmt.Errorf("array index out of bounds: %s", currentKey) + } + if jumperHolder[idx] == nil { + jumperHolder[idx] = make(map[string]interface{}) + } + jumperBackNode = jumper + jumper = jumperHolder[idx] + case reflect.Map: + jumperBackNode = jumper + jumperHolder := jumper.(map[string]interface{}) + nextJumper, ok := jumperHolder[currentKey] + if !ok { + // Auto create missing nodes + nextKey := paths[i+1] + idx, err := ParseJsonPathArrayIndex(nextKey) + if err == nil && idx > -1 { + // 1. .missing_property.[idx]; add an array element + jumperHolder[currentKey] = make([]interface{}, idx+1) + } else { + // 2. .missing_property.other_property; add an object element + jumperHolder[currentKey] = make(map[string]interface{}) + } //if: end processing missing nodes + + jumper = jumperHolder[currentKey] + } else { + // jump to next node + jumper = nextJumper + } + default: + return fmt.Errorf("element cannot be indexed: %s", currentKey) + } + } // end finding last node + + // processing last key and set the value + lastKey := paths[len(paths)-1] + + // when append array is set, the jumper must be a map + if appendArray { + jumperObj, ok := jumper.(map[string]interface{}) + if !ok { + return fmt.Errorf("append array failed because the node before last node is not an object") + } + + if jumperObj[lastKey] == nil { + jumperObj[lastKey] = make([]interface{}, 0) + } + + lastNodeType := reflect.TypeOf(jumperObj[lastKey]).Kind() + if lastNodeType != reflect.Slice { + return fmt.Errorf("append array failed because last node is not an array: %s", lastNodeType.String()) + } + + targetArr := jumperObj[lastKey].([]interface{}) + + switch reflect.TypeOf(value).Kind() { + case reflect.Slice: + sliceValue := reflect.ValueOf(value) + newSlice := jumperObj[lastKey].([]interface{}) + for i := 0; i < sliceValue.Len(); i++ { + newSlice = append(newSlice, sliceValue.Index(i).Interface()) + } + jumperObj[lastKey] = newSlice + default: + jumperObj[lastKey] = append(targetArr, value) + } + + return nil + } + + // set last node + jumperType := reflect.TypeOf(jumper).Kind() + if jumperType == reflect.Slice { + // 1. fixed index array + idx, err := ParseJsonPathArrayIndex(lastKey) + if err != nil { + return fmt.Errorf("cannot set object property inside an array") + } + jumperArray := jumper.([]interface{}) + backNode := jumperBackNode.(map[string]interface{}) + if len(jumperArray) < int(idx+1) { + return fmt.Errorf("cannot modify the target array: index out of bounds: %s", lastKey) + } + jumperArray[idx] = value + backNode[paths[len(paths)-2]] = jumperArray + return nil + } + // 2. normal object + jumberObj, ok := jumper.(map[string]interface{}) + if !ok { + return fmt.Errorf("element cannot be indexed: %s", strings.Join(paths[0:len(paths)-1], ".")) + } + jumberObj[lastKey] = value + return nil +} + +func ParseJsonPathArrayIndex(idxExp string) (int64, error) { + matched, err := regexp.Match("^\\[\\d+\\]$", []byte(idxExp)) + if !matched { + return -1, errors.New("parse index expression failed") + } + if err != nil { + return -1, errors.Wrap(err, "parse index expression failed") + } + + idx := strings.TrimLeft(idxExp, "[") + idx = strings.TrimRight(idx, "]") + idxNum, err := strconv.Atoi(idx) + if err != nil { + return -1, errors.Wrap(err, "illegal index number: "+idx) + } + if idxNum < 0 { + return -1, errors.New("index less than 0") + } + return int64(idxNum), nil +} + +// DeepCopyJSONValue deep copies the passed value, assuming it is a valid JSON representation i.e. only contains +// types produced by json.Unmarshal() and also int64. +// bool, int64, float64, string, []interface{}, map[string]interface{}, json.Number and nil +func deepCopyJSONValuebackup(x interface{}) interface{} { + switch x := x.(type) { + case map[string]interface{}: + if x == nil { + // Typed nil - an interface{} that contains a type map[string]interface{} with a value of nil + return x + } + clone := make(map[string]interface{}, len(x)) + for k, v := range x { + clone[k] = DeepCopyJSONValue(v) + } + return clone + case []interface{}: + if x == nil { + // Typed nil - an interface{} that contains a type []interface{} with a value of nil + return x + } + clone := make([]interface{}, len(x)) + for i, v := range x { + clone[i] = DeepCopyJSONValue(v) + } + return clone + case string, int64, int32, int16, int8, int, bool, float64, float32, nil: // rune is int32 + return x + default: + panic(fmt.Errorf("cannot deep copy %T", x)) + } +} + +func DeepCopyJSONValue(x interface{}) interface{} { + val := reflect.ValueOf(x) + switch val.Kind() { + case reflect.Map: + if val.IsNil() { + return x + } + clone := reflect.MakeMap(val.Type()) + for _, k := range val.MapKeys() { + clone.SetMapIndex(k, reflect.ValueOf(DeepCopyJSONValue(val.MapIndex(k).Interface()))) + } + return clone.Interface() + case reflect.Slice: + if val.IsNil() { + return x + } + clone := reflect.MakeSlice(val.Type(), val.Len(), val.Cap()) + for i := 0; i < val.Len(); i++ { + clone.Index(i).Set(reflect.ValueOf(DeepCopyJSONValue(val.Index(i).Interface()))) + } + return clone.Interface() + case reflect.String, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int, reflect.Bool, reflect.Float64, reflect.Float32: + return x + default: + if val.IsValid() && val.CanInterface() { + return x + } + panic(fmt.Errorf("cannot deep copy %T", x)) + } +}