diff --git a/go.mod b/go.mod index 73c54dc90..7db10aa97 100644 --- a/go.mod +++ b/go.mod @@ -28,8 +28,8 @@ require ( k8s.io/client-go v0.29.0 k8s.io/code-generator v0.26.2 k8s.io/component-base v0.27.2 - k8s.io/klog/v2 v2.110.1 - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 + k8s.io/klog/v2 v2.130.1 + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 k8s.io/utils v0.0.0-20230726121419-3b25d923346b ) @@ -63,6 +63,7 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/deckarep/golang-set v1.8.0 // indirect github.com/docker/cli v24.0.0+incompatible // indirect github.com/docker/docker v26.1.5+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.1 // indirect @@ -76,7 +77,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.0 // indirect github.com/github/go-spdx/v2 v2.2.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -163,22 +164,22 @@ require ( golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/term v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/grpc v1.62.1 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect + k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect k8s.io/kms v0.26.2 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect sigs.k8s.io/controller-runtime v0.15.0 // indirect @@ -194,7 +195,7 @@ replace ( k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20231102051132-bc0a03b4342c k8s.io/apiserver => k8s.io/apiserver v0.0.0-20231101172914-798e645af694 k8s.io/client-go => k8s.io/client-go v0.0.0-20231101171620-66e57f767515 - k8s.io/code-generator => k8s.io/code-generator v0.0.0-20231101170854-66e74b777ae2 + k8s.io/code-generator => k8s.io/code-generator v0.0.0-20240727175048-b53d16e2b339 k8s.io/component-base => k8s.io/component-base v0.0.0-20231101172256-4b808112b779 k8s.io/kms => k8s.io/kms v0.0.0-20231101172519-156d7b7a0cca ) diff --git a/go.sum b/go.sum index 5cdff89ea..cf410f92d 100644 --- a/go.sum +++ b/go.sum @@ -723,6 +723,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= @@ -799,12 +801,10 @@ github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2C github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= @@ -903,7 +903,6 @@ github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDIt github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -1719,8 +1718,8 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1768,7 +1767,6 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1795,8 +1793,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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= @@ -2049,8 +2047,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2098,19 +2096,18 @@ k8s.io/apiserver v0.0.0-20231101172914-798e645af694 h1:HA4DTzrYeaaK4xnOadvOqaA0y k8s.io/apiserver v0.0.0-20231101172914-798e645af694/go.mod h1:wSuB7QHyj8n69e2q5OGtfCEZcd2no9kpw68np1MNshM= k8s.io/client-go v0.0.0-20231101171620-66e57f767515 h1:iAC4m6tArcsiOFI2QMYzpMxGVksKBSH7k5whcUJLtHw= k8s.io/client-go v0.0.0-20231101171620-66e57f767515/go.mod h1:BuOU+TpCaVunHx4J07MTcND+TbxdXG/OLKFu6HXF4Mc= -k8s.io/code-generator v0.0.0-20231101170854-66e74b777ae2 h1:UJVzskIHkTpub0N8Z1hxHtsiYqCpEueurWmnw/s3ytE= -k8s.io/code-generator v0.0.0-20231101170854-66e74b777ae2/go.mod h1:C1oDIDCuN+hZsr8bZVFUp6dsOKvvMZ6jcmE4SFQn//8= +k8s.io/code-generator v0.0.0-20240727175048-b53d16e2b339 h1:hUQUJfAKvCSI58b71N32zYLfg+4eT2DPzD55MZCsYAA= +k8s.io/code-generator v0.0.0-20240727175048-b53d16e2b339/go.mod h1:EUn13T3A/CaDKcstPAAr74dTsuy2Fzz63w8+i/ghW0c= k8s.io/component-base v0.0.0-20231101172256-4b808112b779 h1:Z7qdGV7DKgU9asDkQQusUK8Gzl0dDHrOS59H6+6z80s= k8s.io/component-base v0.0.0-20231101172256-4b808112b779/go.mod h1:nC9MQrFtSP4UJAAPfBF4KVVbenBrNryFuAZJI0T3tp0= -k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= -k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -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/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= +k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kms v0.0.0-20231101172519-156d7b7a0cca h1:PEB8+fyzk2evbBrvOpmYUZN4JfJrWGH+3BQ8epezf3Y= k8s.io/kms v0.0.0-20231101172519-156d7b7a0cca/go.mod h1:tzWlveijOvrAKlu2RgEusoRaEQVEhtA1Xjh/UNi7r70= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= @@ -2159,7 +2156,6 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm 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.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 694ac0079..1f0000d9a 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -21,26 +21,33 @@ set -o pipefail SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)} -# generate the code with: -# --output-base because this script should also be able to run inside the vendor dir of -# k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir -# instead of the $GOPATH directly. For normal projects this can be dropped. - -# intentionally omits applyconfigurations because it generates broken code for our types -"${CODEGEN_PKG}/generate-groups.sh" "client,deepcopy,informer,lister,defaulter,conversion,openapi" \ - github.com/kubescape/storage/pkg/generated \ - github.com/kubescape/storage/pkg/apis \ - "softwarecomposition:v1beta1" \ - --output-base "$(dirname "${BASH_SOURCE[0]}")/../../../.." \ - --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt - -"${CODEGEN_PKG}/generate-internal-groups.sh" "deepcopy,defaulter,conversion,openapi" \ - github.com/kubescape/storage/pkg/generated \ - github.com/kubescape/storage/pkg/apis \ - github.com/kubescape/storage/pkg/apis \ - "softwarecomposition:v1beta1" \ - --output-base "$(dirname "${BASH_SOURCE[0]}")/../../../.." \ - --go-header-file "${SCRIPT_ROOT}/hack/boilerplate.go.txt" - -# To use your own boilerplate text append: -# --go-header-file "${SCRIPT_ROOT}/hack/custom-boilerplate.go.txt" +source "${CODEGEN_PKG}/kube_codegen.sh" + +THIS_PKG="github.com/kubescape/storage" + +kube::codegen::gen_helpers \ + --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ + "${SCRIPT_ROOT}/pkg/apis" + +if [[ -n "${API_KNOWN_VIOLATIONS_DIR:-}" ]]; then + report_filename="${API_KNOWN_VIOLATIONS_DIR}/sample_apiserver_violation_exceptions.list" + if [[ "${UPDATE_API_KNOWN_VIOLATIONS:-}" == "true" ]]; then + update_report="--update-report" + fi +fi + +kube::codegen::gen_openapi \ + --output-dir "${SCRIPT_ROOT}/pkg/generated/openapi" \ + --output-pkg "${THIS_PKG}/pkg/generated/openapi" \ + --report-filename "${report_filename:-"/dev/null"}" \ + ${update_report:+"${update_report}"} \ + --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ + "${SCRIPT_ROOT}/pkg/apis" + +kube::codegen::gen_client \ + --with-watch \ + --with-applyconfig \ + --output-dir "${SCRIPT_ROOT}/pkg/generated" \ + --output-pkg "${THIS_PKG}/pkg/generated" \ + --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ + "${SCRIPT_ROOT}/pkg/apis" diff --git a/pkg/apis/softwarecomposition/consts/consts.go b/pkg/apis/softwarecomposition/consts/consts.go new file mode 100644 index 000000000..3fb5090cf --- /dev/null +++ b/pkg/apis/softwarecomposition/consts/consts.go @@ -0,0 +1,15 @@ +package consts + +type NetworkDirection string + +const ( + Inbound NetworkDirection = "inbound" + Outbound NetworkDirection = "outbound" +) + +type IsInternal string + +const ( + True IsInternal = "true" + False IsInternal = "false" +) diff --git a/pkg/apis/softwarecomposition/types.go b/pkg/apis/softwarecomposition/types.go index 24e3619f3..2b0bb16cf 100644 --- a/pkg/apis/softwarecomposition/types.go +++ b/pkg/apis/softwarecomposition/types.go @@ -17,9 +17,11 @@ limitations under the License. package softwarecomposition import ( + "fmt" "strings" "github.com/containers/common/pkg/seccomp" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/consts" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -283,6 +285,7 @@ type ApplicationProfileContainer struct { Opens []OpenCalls Syscalls []string SeccompProfile SingleSeccompProfile + Endpoints []HTTPEndpoint } type ExecCalls struct { @@ -672,6 +675,72 @@ type Arg struct { Op seccomp.Operator } +type HTTPEndpoint struct { + Endpoint string + Methods []string + Internal consts.IsInternal + Direction consts.NetworkDirection + Headers map[string][]string +} + +func (e HTTPEndpoint) String() string { + const sep = "␟" + var s strings.Builder + + // Append Endpoint + s.WriteString(e.Endpoint) + + // Append Methods + if len(e.Methods) > 0 { + if s.Len() > 0 { + s.WriteString(sep) + } + s.WriteString(strings.Join(e.Methods, ",")) + } + + // Append Internal status + if e.Internal == consts.True { + if s.Len() > 0 { + s.WriteString(sep) + } + s.WriteString("Internal") + } + + // Append Direction + if e.Direction != "" { + if s.Len() > 0 { + s.WriteString(sep) + } + // Capitalize the first letter of the direction + s.WriteString(strings.Title(string(e.Direction))) + } + + // Append Headers + if len(e.Headers) > 0 { + // Define the order of headers + orderedHeaders := []string{"Content-Type", "Authorization"} + + for _, k := range orderedHeaders { + if values, ok := e.Headers[k]; ok { + if s.Len() > 0 { + s.WriteString(sep) + } + s.WriteString(fmt.Sprintf("%s: %s", k, strings.Join(values, ","))) + } + } + } + + return s.String() +} + +func (e *HTTPEndpoint) Equal(other *HTTPEndpoint) bool { + if e == nil || other == nil { + return e == other + } + return e.Endpoint == other.Endpoint && e.Direction == other.Direction && e.Internal == other.Internal + +} + type SpecBase struct { Disabled bool } diff --git a/pkg/apis/softwarecomposition/types_test.go b/pkg/apis/softwarecomposition/types_test.go index bd018d291..9776a2e37 100644 --- a/pkg/apis/softwarecomposition/types_test.go +++ b/pkg/apis/softwarecomposition/types_test.go @@ -3,6 +3,7 @@ package softwarecomposition import ( "testing" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/consts" "github.com/stretchr/testify/assert" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -345,3 +346,44 @@ func TestOpenCalls_String(t *testing.T) { }) } } + +func TestHTTPEndpoint_String(t *testing.T) { + tests := []struct { + name string + e HTTPEndpoint + want string + }{ + { + name: "Empty", + e: HTTPEndpoint{}, + want: "", + }, + { + name: "Endpoint and Methods only", + e: HTTPEndpoint{ + Endpoint: "/api/v1/users", + Methods: []string{"GET", "POST"}, + }, + want: "/api/v1/users␟GET,POST", + }, + { + name: "Full HTTPEndpoint", + e: HTTPEndpoint{ + Endpoint: "/api/v1/users", + Methods: []string{"GET", "POST"}, + Internal: consts.True, + Direction: consts.Inbound, + Headers: map[string][]string{ + "Content-Type": {"application/json"}, + "Authorization": {"Bearer token123", "ApiKey abcdef"}, + }, + }, + want: "/api/v1/users␟GET,POST␟Internal␟Inbound␟Content-Type: application/json␟Authorization: Bearer token123,ApiKey abcdef", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, tt.e.String(), "String()") + }) + } +} diff --git a/pkg/apis/softwarecomposition/v1beta1/types.go b/pkg/apis/softwarecomposition/v1beta1/types.go index e1dd64704..20bd1c823 100644 --- a/pkg/apis/softwarecomposition/v1beta1/types.go +++ b/pkg/apis/softwarecomposition/v1beta1/types.go @@ -18,6 +18,7 @@ package v1beta1 import ( "github.com/containers/common/pkg/seccomp" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/consts" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -267,6 +268,9 @@ type ApplicationProfileContainer struct { Opens []OpenCalls `json:"opens" patchStrategy:"merge" patchMergeKey:"path"` Syscalls []string `json:"syscalls"` SeccompProfile SingleSeccompProfile `json:"seccompProfile,omitempty"` + // +patchMergeKey=endpoint + // +patchStrategy=merge + Endpoints []HTTPEndpoint `json:"endpoints" patchStrategy:"merge" patchMergeKey:"endpoint"` } type ExecCalls struct { @@ -528,7 +532,7 @@ type SBOMSyftFiltered struct { metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` Spec SBOMSyftSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - Status SBOMSyftStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` + Status SBOMSyftStatus `json:"status,omitempty " protobuf:"bytes,3,opt,name=status"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -616,6 +620,14 @@ type Syscall struct { Args []*Arg `json:"args,omitempty"` } +type HTTPEndpoint struct { + Endpoint string `json:"endpoint,omitempty"` + Methods []string `json:"methods,omitempty"` + Internal consts.IsInternal `json:"internal,omitempty"` + Direction consts.NetworkDirection `json:"direction,omitempty"` + Headers map[string][]string `json:"headers,omitempty"` +} + // Arg defines the specific syscall in seccomp. type Arg struct { // the index for syscall arguments in seccomp diff --git a/pkg/apis/softwarecomposition/v1beta1/zz_generated.conversion.go b/pkg/apis/softwarecomposition/v1beta1/zz_generated.conversion.go index 76567ab6c..4816f1e76 100644 --- a/pkg/apis/softwarecomposition/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/softwarecomposition/v1beta1/zz_generated.conversion.go @@ -27,6 +27,7 @@ import ( seccomp "github.com/containers/common/pkg/seccomp" softwarecomposition "github.com/kubescape/storage/pkg/apis/softwarecomposition" + consts "github.com/kubescape/storage/pkg/apis/softwarecomposition/consts" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" @@ -500,6 +501,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*HTTPEndpoint)(nil), (*softwarecomposition.HTTPEndpoint)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_HTTPEndpoint_To_softwarecomposition_HTTPEndpoint(a.(*HTTPEndpoint), b.(*softwarecomposition.HTTPEndpoint), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*softwarecomposition.HTTPEndpoint)(nil), (*HTTPEndpoint)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_softwarecomposition_HTTPEndpoint_To_v1beta1_HTTPEndpoint(a.(*softwarecomposition.HTTPEndpoint), b.(*HTTPEndpoint), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*HTTPIngressPath)(nil), (*softwarecomposition.HTTPIngressPath)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_HTTPIngressPath_To_softwarecomposition_HTTPIngressPath(a.(*HTTPIngressPath), b.(*softwarecomposition.HTTPIngressPath), scope) }); err != nil { @@ -2156,6 +2167,7 @@ func autoConvert_v1beta1_ApplicationProfileContainer_To_softwarecomposition_Appl if err := Convert_v1beta1_SingleSeccompProfile_To_softwarecomposition_SingleSeccompProfile(&in.SeccompProfile, &out.SeccompProfile, s); err != nil { return err } + out.Endpoints = *(*[]softwarecomposition.HTTPEndpoint)(unsafe.Pointer(&in.Endpoints)) return nil } @@ -2173,6 +2185,7 @@ func autoConvert_softwarecomposition_ApplicationProfileContainer_To_v1beta1_Appl if err := Convert_softwarecomposition_SingleSeccompProfile_To_v1beta1_SingleSeccompProfile(&in.SeccompProfile, &out.SeccompProfile, s); err != nil { return err } + out.Endpoints = *(*[]HTTPEndpoint)(unsafe.Pointer(&in.Endpoints)) return nil } @@ -3177,6 +3190,34 @@ func Convert_softwarecomposition_GrypePackage_To_v1beta1_GrypePackage(in *softwa return autoConvert_softwarecomposition_GrypePackage_To_v1beta1_GrypePackage(in, out, s) } +func autoConvert_v1beta1_HTTPEndpoint_To_softwarecomposition_HTTPEndpoint(in *HTTPEndpoint, out *softwarecomposition.HTTPEndpoint, s conversion.Scope) error { + out.Endpoint = in.Endpoint + out.Methods = *(*[]string)(unsafe.Pointer(&in.Methods)) + out.Internal = consts.IsInternal(in.Internal) + out.Direction = consts.NetworkDirection(in.Direction) + out.Headers = *(*map[string][]string)(unsafe.Pointer(&in.Headers)) + return nil +} + +// Convert_v1beta1_HTTPEndpoint_To_softwarecomposition_HTTPEndpoint is an autogenerated conversion function. +func Convert_v1beta1_HTTPEndpoint_To_softwarecomposition_HTTPEndpoint(in *HTTPEndpoint, out *softwarecomposition.HTTPEndpoint, s conversion.Scope) error { + return autoConvert_v1beta1_HTTPEndpoint_To_softwarecomposition_HTTPEndpoint(in, out, s) +} + +func autoConvert_softwarecomposition_HTTPEndpoint_To_v1beta1_HTTPEndpoint(in *softwarecomposition.HTTPEndpoint, out *HTTPEndpoint, s conversion.Scope) error { + out.Endpoint = in.Endpoint + out.Methods = *(*[]string)(unsafe.Pointer(&in.Methods)) + out.Internal = consts.IsInternal(in.Internal) + out.Direction = consts.NetworkDirection(in.Direction) + out.Headers = *(*map[string][]string)(unsafe.Pointer(&in.Headers)) + return nil +} + +// Convert_softwarecomposition_HTTPEndpoint_To_v1beta1_HTTPEndpoint is an autogenerated conversion function. +func Convert_softwarecomposition_HTTPEndpoint_To_v1beta1_HTTPEndpoint(in *softwarecomposition.HTTPEndpoint, out *HTTPEndpoint, s conversion.Scope) error { + return autoConvert_softwarecomposition_HTTPEndpoint_To_v1beta1_HTTPEndpoint(in, out, s) +} + func autoConvert_v1beta1_HTTPIngressPath_To_softwarecomposition_HTTPIngressPath(in *HTTPIngressPath, out *softwarecomposition.HTTPIngressPath, s conversion.Scope) error { out.Path = in.Path out.PathType = (*softwarecomposition.PathType)(unsafe.Pointer(in.PathType)) diff --git a/pkg/apis/softwarecomposition/v1beta1/zz_generated.deepcopy.go b/pkg/apis/softwarecomposition/v1beta1/zz_generated.deepcopy.go index e60a7620c..c13811bc0 100644 --- a/pkg/apis/softwarecomposition/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/softwarecomposition/v1beta1/zz_generated.deepcopy.go @@ -233,6 +233,13 @@ func (in *ApplicationProfileContainer) DeepCopyInto(out *ApplicationProfileConta copy(*out, *in) } in.SeccompProfile.DeepCopyInto(&out.SeccompProfile) + if in.Endpoints != nil { + in, out := &in.Endpoints, &out.Endpoints + *out = make([]HTTPEndpoint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -1266,6 +1273,42 @@ func (in *GrypePackage) DeepCopy() *GrypePackage { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPEndpoint) DeepCopyInto(out *HTTPEndpoint) { + *out = *in + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPEndpoint. +func (in *HTTPEndpoint) DeepCopy() *HTTPEndpoint { + if in == nil { + return nil + } + out := new(HTTPEndpoint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPIngressPath) DeepCopyInto(out *HTTPIngressPath) { *out = *in diff --git a/pkg/apis/softwarecomposition/zz_generated.deepcopy.go b/pkg/apis/softwarecomposition/zz_generated.deepcopy.go index a38560193..b241e84ce 100644 --- a/pkg/apis/softwarecomposition/zz_generated.deepcopy.go +++ b/pkg/apis/softwarecomposition/zz_generated.deepcopy.go @@ -233,6 +233,13 @@ func (in *ApplicationProfileContainer) DeepCopyInto(out *ApplicationProfileConta copy(*out, *in) } in.SeccompProfile.DeepCopyInto(&out.SeccompProfile) + if in.Endpoints != nil { + in, out := &in.Endpoints, &out.Endpoints + *out = make([]HTTPEndpoint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -1266,6 +1273,42 @@ func (in *GrypePackage) DeepCopy() *GrypePackage { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPEndpoint) DeepCopyInto(out *HTTPEndpoint) { + *out = *in + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPEndpoint. +func (in *HTTPEndpoint) DeepCopy() *HTTPEndpoint { + if in == nil { + return nil + } + out := new(HTTPEndpoint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPIngressPath) DeepCopyInto(out *HTTPIngressPath) { *out = *in diff --git a/pkg/apiserver/scheme_test.go b/pkg/apiserver/scheme_test.go deleted file mode 100644 index f78bc414a..000000000 --- a/pkg/apiserver/scheme_test.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package apiserver - -import ( - "math/rand" - "regexp" - "testing" - - "github.com/kubescape/storage/pkg/apis/softwarecomposition" - wardlefuzzer "github.com/kubescape/storage/pkg/apis/softwarecomposition/fuzzer" - - "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" - "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" - metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" - "k8s.io/apimachinery/pkg/runtime/schema" - runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" -) - -func TestRoundTripTypes(t *testing.T) { - fuzzingFuncs := wardlefuzzer.Funcs - scheme := Scheme - - codecFactory := runtimeserializer.NewCodecFactory(scheme) - f := fuzzer.FuzzerFor( - fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, fuzzingFuncs), - rand.NewSource(rand.Int63()), - codecFactory, - ) - f.NumElements(1, 2) - f.NilChance(0) - - nonRoundTrippableTypes := map[schema.GroupVersionKind]bool{ - // Syft types use custom JSON unmarshaling, so they are not round-trippable by definition - softwarecomposition.SchemeGroupVersion.WithKind("SBOMSyft"): true, - softwarecomposition.SchemeGroupVersion.WithKind("SBOMSyftList"): true, - softwarecomposition.SchemeGroupVersion.WithKind("SBOMSyftFiltered"): true, - softwarecomposition.SchemeGroupVersion.WithKind("SBOMSyftFilteredList"): true, - } - - skippedFields := []string{ - "SnippetAttributionTexts", - "SpecialID", - "IsUnpackaged", - "IsFilesAnalyzedTagPresent", - "ManagedFields", - "SnippetSPDXIdentifier", - "Snippets", - "DocumentRefID", - "ElementRefID", - // Not exported - "AnnotationSPDXIdentifier", - } - for idx := range skippedFields { - skipPattern := regexp.MustCompile(skippedFields[idx]) - f.SkipFieldsWithPattern(skipPattern) - } - - roundtrip.RoundTripTypesWithoutProtobuf(t, scheme, codecFactory, f, nonRoundTrippableTypes) -} diff --git a/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/applicationprofile.go b/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/applicationprofile.go index 6e4499de1..2cbdfb527 100644 --- a/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/applicationprofile.go +++ b/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/applicationprofile.go @@ -181,6 +181,7 @@ func (c *applicationProfiles) DeleteCollection(ctx context.Context, opts v1.Dele // Patch applies the patch and returns the patched applicationProfile. func (c *applicationProfiles) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ApplicationProfile, err error) { + result = &v1beta1.ApplicationProfile{} err = c.client.Patch(pt). Namespace(c.ns). @@ -191,5 +192,6 @@ func (c *applicationProfiles) Patch(ctx context.Context, name string, pt types.P Body(data). Do(ctx). Into(result) - return + + return result, err } diff --git a/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/fake/fake_softwarecomposition_client.go b/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/fake/fake_softwarecomposition_client.go index bcfd828a9..ccd3b58ad 100644 --- a/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/fake/fake_softwarecomposition_client.go +++ b/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/fake/fake_softwarecomposition_client.go @@ -105,4 +105,4 @@ func (c *FakeSpdxV1beta1) WorkloadConfigurationScanSummaries(namespace string) v func (c *FakeSpdxV1beta1) RESTClient() rest.Interface { var ret *rest.RESTClient return ret -} +} \ No newline at end of file diff --git a/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/generated_expansion.go index 6246f76fc..f42d0278d 100644 --- a/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/generated_expansion.go @@ -26,6 +26,8 @@ type ConfigurationScanSummaryExpansion interface{} type GeneratedNetworkPolicyExpansion interface{} +type HTTPEndpointExpansion interface{} + type KnownServerExpansion interface{} type NetworkNeighborhoodExpansion interface{} diff --git a/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/softwarecomposition_client.go b/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/softwarecomposition_client.go index 17de7853f..78aae2bde 100644 --- a/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/softwarecomposition_client.go +++ b/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/softwarecomposition_client.go @@ -189,4 +189,4 @@ func (c *SpdxV1beta1Client) RESTClient() rest.Interface { return nil } return c.restClient -} +} \ No newline at end of file diff --git a/pkg/generated/listers/softwarecomposition/v1beta1/expansion_generated.go b/pkg/generated/listers/softwarecomposition/v1beta1/expansion_generated.go index 7241b89f8..5ea54a3b9 100644 --- a/pkg/generated/listers/softwarecomposition/v1beta1/expansion_generated.go +++ b/pkg/generated/listers/softwarecomposition/v1beta1/expansion_generated.go @@ -50,6 +50,14 @@ type GeneratedNetworkPolicyListerExpansion interface{} // GeneratedNetworkPolicyNamespaceLister. type GeneratedNetworkPolicyNamespaceListerExpansion interface{} +// HTTPEndpointListerExpansion allows custom methods to be added to +// HTTPEndpointLister. +type HTTPEndpointListerExpansion interface{} + +// HTTPEndpointNamespaceListerExpansion allows custom methods to be added to +// HTTPEndpointNamespaceLister. +type HTTPEndpointNamespaceListerExpansion interface{} + // KnownServerListerExpansion allows custom methods to be added to // KnownServerLister. type KnownServerListerExpansion interface{} diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index b626e3d95..13dc49318 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -19,8 +19,6 @@ limitations under the License. // Code generated by openapi-gen. DO NOT EDIT. -// This file was autogenerated by openapi-gen. Do not edit it manually! - package openapi import ( @@ -77,6 +75,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.GeneratedNetworkPolicyList": schema_pkg_apis_softwarecomposition_v1beta1_GeneratedNetworkPolicyList(ref), "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.GrypeDocument": schema_pkg_apis_softwarecomposition_v1beta1_GrypeDocument(ref), "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.GrypePackage": schema_pkg_apis_softwarecomposition_v1beta1_GrypePackage(ref), + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.HTTPEndpoint": schema_pkg_apis_softwarecomposition_v1beta1_HTTPEndpoint(ref), "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.HTTPIngressPath": schema_pkg_apis_softwarecomposition_v1beta1_HTTPIngressPath(ref), "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.HTTPIngressRuleValue": schema_pkg_apis_softwarecomposition_v1beta1_HTTPIngressRuleValue(ref), "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.IPBlock": schema_pkg_apis_softwarecomposition_v1beta1_IPBlock(ref), @@ -639,12 +638,31 @@ func schema_pkg_apis_softwarecomposition_v1beta1_ApplicationProfileContainer(ref Ref: ref("github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.SingleSeccompProfile"), }, }, + "endpoints": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-patch-merge-key": "endpoint", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.HTTPEndpoint"), + }, + }, + }, + }, + }, }, - Required: []string{"capabilities", "execs", "opens", "syscalls"}, + Required: []string{"capabilities", "execs", "opens", "syscalls", "endpoints"}, }, }, Dependencies: []string{ - "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.ExecCalls", "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.OpenCalls", "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.SingleSeccompProfile"}, + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.ExecCalls", "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.HTTPEndpoint", "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.OpenCalls", "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1.SingleSeccompProfile"}, } } @@ -2585,6 +2603,72 @@ func schema_pkg_apis_softwarecomposition_v1beta1_GrypePackage(ref common.Referen } } +func schema_pkg_apis_softwarecomposition_v1beta1_HTTPEndpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "endpoint": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "methods": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "internal": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "direction": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "headers": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_softwarecomposition_v1beta1_HTTPIngressPath(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/registry/file/applicationprofile_processor.go b/pkg/registry/file/applicationprofile_processor.go index 704036858..00342c9f8 100644 --- a/pkg/registry/file/applicationprofile_processor.go +++ b/pkg/registry/file/applicationprofile_processor.go @@ -5,8 +5,11 @@ import ( "strconv" mapset "github.com/deckarep/golang-set/v2" + "github.com/kubescape/go-logger" + loggerhelpers "github.com/kubescape/go-logger/helpers" "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" "github.com/kubescape/storage/pkg/apis/softwarecomposition" + "github.com/kubescape/storage/pkg/registry/file/dynamicpathdetector" "k8s.io/apimachinery/pkg/runtime" ) @@ -46,6 +49,12 @@ func (a ApplicationProfileProcessor) PreSave(object runtime.Object) error { } func deflateApplicationProfileContainer(container softwarecomposition.ApplicationProfileContainer) softwarecomposition.ApplicationProfileContainer { + endpoints, err := dynamicpathdetector.AnalyzeEndpoints(&container.Endpoints, dynamicpathdetector.NewPathAnalyzer()) + if err != nil { + logger.L().Debug("failed to analyze endpoints", loggerhelpers.Error(err)) + endpoints = container.Endpoints + } + return softwarecomposition.ApplicationProfileContainer{ Name: container.Name, Capabilities: mapset.Sorted(mapset.NewThreadUnsafeSet(container.Capabilities...)), @@ -53,5 +62,6 @@ func deflateApplicationProfileContainer(container softwarecomposition.Applicatio Opens: deflateStringer(container.Opens), Syscalls: mapset.Sorted(mapset.NewThreadUnsafeSet(container.Syscalls...)), SeccompProfile: container.SeccompProfile, + Endpoints: endpoints, } } diff --git a/pkg/registry/file/applicationprofile_processor_test.go b/pkg/registry/file/applicationprofile_processor_test.go index cb4d30533..bffab9c8f 100644 --- a/pkg/registry/file/applicationprofile_processor_test.go +++ b/pkg/registry/file/applicationprofile_processor_test.go @@ -7,6 +7,7 @@ import ( "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" "github.com/kubescape/storage/pkg/apis/softwarecomposition" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/consts" "github.com/stretchr/testify/assert" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -60,6 +61,15 @@ func TestApplicationProfileProcessor_PreSave(t *testing.T) { Opens: []softwarecomposition.OpenCalls{ {Path: "/etc/hosts", Flags: []string{"O_CLOEXEC", "O_RDONLY"}}, }, + Endpoints: []softwarecomposition.HTTPEndpoint{ + { + Endpoint: "http://localhost:8080", + Methods: []string{"GET"}, + Internal: consts.False, + Direction: consts.Inbound, + Headers: map[string][]string{}, + }, + }, }, }, }, @@ -115,6 +125,15 @@ func TestApplicationProfileProcessor_PreSave(t *testing.T) { {Path: "/etc/hosts", Flags: []string{"O_CLOEXEC", "O_RDONLY"}}, }, Syscalls: []string{}, + Endpoints: []softwarecomposition.HTTPEndpoint{ + { + Endpoint: "localhost/", + Methods: []string{"GET"}, + Internal: consts.False, + Direction: consts.Inbound, + Headers: map[string][]string{}, + }, + }, }, }, }, diff --git a/pkg/registry/file/dynamicpathdetector/analyze_endpoints.go b/pkg/registry/file/dynamicpathdetector/analyze_endpoints.go new file mode 100644 index 000000000..b39b6437d --- /dev/null +++ b/pkg/registry/file/dynamicpathdetector/analyze_endpoints.go @@ -0,0 +1,131 @@ +package dynamicpathdetector + +import ( + "fmt" + "net/url" + "strings" + + mapset "github.com/deckarep/golang-set/v2" + types "github.com/kubescape/storage/pkg/apis/softwarecomposition" +) + +func AnalyzeEndpoints(endpoints *[]types.HTTPEndpoint, analyzer *PathAnalyzer) ([]types.HTTPEndpoint, error) { + var newEndpoints []types.HTTPEndpoint + MergeDuplicateEndpoints(endpoints) + for _, endpoint := range *endpoints { + AnalyzeURL(endpoint.Endpoint, analyzer) + } + + for _, endpoint := range *endpoints { + processedEndpoint, err := ProcessEndpoint(&endpoint, analyzer, newEndpoints) + if processedEndpoint == nil && err == nil || err != nil { + continue + } else { + newEndpoints = append(newEndpoints, *processedEndpoint) + } + } + + return newEndpoints, nil +} + +func ProcessEndpoint(endpoint *types.HTTPEndpoint, analyzer *PathAnalyzer, newEndpoints []types.HTTPEndpoint) (*types.HTTPEndpoint, error) { + url, err := AnalyzeURL(endpoint.Endpoint, analyzer) + if err != nil { + return nil, err + } + + if url != endpoint.Endpoint { + + // Check if this dynamic exists + for i, e := range newEndpoints { + if e.Endpoint == url { + newEndpoints[i].Methods = mergeMethods(e.Methods, endpoint.Methods) + newEndpoints[i].Headers = mergeHeaders(e.Headers, endpoint.Headers) + return nil, nil + } + } + + dynamicEndpoint := types.HTTPEndpoint{ + Endpoint: url, + Methods: endpoint.Methods, + Internal: endpoint.Internal, + Direction: endpoint.Direction, + Headers: endpoint.Headers, + } + + return &dynamicEndpoint, nil + } + + return endpoint, nil +} + +func AnalyzeURL(urlString string, analyzer *PathAnalyzer) (string, error) { + if !strings.HasPrefix(urlString, "http://") && !strings.HasPrefix(urlString, "https://") { + urlString = "http://" + urlString + } + + parsedURL, err := url.Parse(urlString) + if err != nil { + return "", err + } + + hostname := parsedURL.Hostname() + + path, _ := analyzer.AnalyzePath(parsedURL.Path, hostname) + if path == "/." { + path = "/" + } + return hostname + path, nil +} + +func MergeDuplicateEndpoints(endpoints *[]types.HTTPEndpoint) { + seen := make(map[string]*types.HTTPEndpoint) + newEndpoints := make([]types.HTTPEndpoint, 0) + + for i := range *endpoints { + endpoint := &(*endpoints)[i] + key := getEndpointKey(endpoint) + + if existing, found := seen[key]; found { + existing.Methods = mergeMethods(existing.Methods, endpoint.Methods) + existing.Headers = mergeHeaders(existing.Headers, endpoint.Headers) + } else { + seen[key] = endpoint + newEndpoints = append(newEndpoints, *endpoint) + } + } + + *endpoints = newEndpoints +} + +func getEndpointKey(endpoint *types.HTTPEndpoint) string { + return fmt.Sprintf("%s|%v|%v", endpoint.Endpoint, endpoint.Internal, endpoint.Direction) +} + +func mergeHeaders(existing, new map[string][]string) map[string][]string { + + for k, v := range new { + if _, exists := existing[k]; exists { + set := mapset.NewSet[string](append(existing[k], v...)...) + existing[k] = set.ToSlice() + } else { + existing[k] = v + } + } + + return existing +} + +func mergeMethods(existing, new []string) []string { + methodSet := make(map[string]bool) + for _, m := range existing { + methodSet[m] = true + } + for _, m := range new { + if !methodSet[m] { + existing = append(existing, m) + methodSet[m] = true + } + } + return existing +} diff --git a/pkg/registry/file/dynamicpathdetector/analyzer.go b/pkg/registry/file/dynamicpathdetector/analyzer.go new file mode 100644 index 000000000..f1af57047 --- /dev/null +++ b/pkg/registry/file/dynamicpathdetector/analyzer.go @@ -0,0 +1,137 @@ +package dynamicpathdetector + +import ( + pathUtils "path" + "strings" +) + +func NewPathAnalyzer() *PathAnalyzer { + return &PathAnalyzer{ + RootNodes: make(map[string]*SegmentNode), + } +} +func (ua *PathAnalyzer) AnalyzePath(path, identifier string) (string, error) { + path = pathUtils.Clean(path) + node, exists := ua.RootNodes[identifier] + if !exists { + node = &SegmentNode{ + SegmentName: identifier, + Count: 0, + Children: make(map[string]*SegmentNode), + } + ua.RootNodes[identifier] = node + } + + segments := strings.Split(strings.Trim(path, "/"), "/") + + return ua.processSegments(node, segments), nil +} + +func (ua *PathAnalyzer) processSegments(node *SegmentNode, segments []string) string { + resultPath := []string{} + currentNode := node + for _, segment := range segments { + currentNode = ua.processSegment(currentNode, segment) + ua.updateNodeStats(currentNode) + resultPath = append(resultPath, currentNode.SegmentName) + } + return "/" + strings.Join(resultPath, "/") + +} + +func (ua *PathAnalyzer) processSegment(node *SegmentNode, segment string) *SegmentNode { + + switch { + case segment == dynamicIdentifier: + return ua.handleDynamicSegment(node) + case KeyInMap(node.Children, segment) || node.IsNextDynamic(): + child, exists := node.Children[segment] + return ua.handleExistingSegment(node, child, exists) + default: + return ua.handleNewSegment(node, segment) + + } +} + +func (ua *PathAnalyzer) handleExistingSegment(node *SegmentNode, child *SegmentNode, exists bool) *SegmentNode { + if exists { + return child + } else { + return node.Children[dynamicIdentifier] + } +} + +func (ua *PathAnalyzer) handleNewSegment(node *SegmentNode, segment string) *SegmentNode { + node.Count++ + newNode := &SegmentNode{ + SegmentName: segment, + Count: 0, + Children: make(map[string]*SegmentNode), + } + node.Children[segment] = newNode + return newNode +} + +func (ua *PathAnalyzer) handleDynamicSegment(node *SegmentNode) *SegmentNode { + if dynamicChild, exists := node.Children[dynamicIdentifier]; exists { + return dynamicChild + } else { + return ua.createDynamicNode(node) + } +} + +func (ua *PathAnalyzer) createDynamicNode(node *SegmentNode) *SegmentNode { + dynamicNode := &SegmentNode{ + SegmentName: dynamicIdentifier, + Count: 0, + Children: make(map[string]*SegmentNode), + } + + // Copy all existing children to the new dynamic node + for _, child := range node.Children { + shallowChildrenCopy(child, dynamicNode) + } + + // Replace all children with the new dynamic node + node.Children = map[string]*SegmentNode{ + dynamicIdentifier: dynamicNode, + } + + return dynamicNode +} + +func (ua *PathAnalyzer) updateNodeStats(node *SegmentNode) { + if node.Count > threshold && !node.IsNextDynamic() { + + dynamicChild := &SegmentNode{ + SegmentName: dynamicIdentifier, + Count: 0, + Children: make(map[string]*SegmentNode), + } + + // Copy all descendants + for _, child := range node.Children { + shallowChildrenCopy(child, dynamicChild) + } + + node.Children = map[string]*SegmentNode{ + dynamicIdentifier: dynamicChild, + } + } +} + +func shallowChildrenCopy(src, dst *SegmentNode) { + for segmentName := range src.Children { + if !KeyInMap(dst.Children, segmentName) { + dst.Children[segmentName] = src.Children[segmentName] + } else { + dst.Children[segmentName].Count += src.Children[segmentName].Count + shallowChildrenCopy(src.Children[segmentName], dst.Children[segmentName]) + } + } +} + +func KeyInMap[T any](TestMap map[string]T, key string) bool { + _, ok := TestMap[key] + return ok +} diff --git a/pkg/registry/file/dynamicpathdetector/tests/benchamark_test.go b/pkg/registry/file/dynamicpathdetector/tests/benchamark_test.go new file mode 100644 index 000000000..8a277ca53 --- /dev/null +++ b/pkg/registry/file/dynamicpathdetector/tests/benchamark_test.go @@ -0,0 +1,116 @@ +package dynamicpathdetectortests + +import ( + "fmt" + "math/rand" + "strings" + "testing" + + "github.com/kubescape/storage/pkg/registry/file/dynamicpathdetector" +) + +func BenchmarkAnalyzePath(b *testing.B) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + paths := generateMixedPaths(10000, 0) // 0 means use default mixed lengths + + identifier := "test" + + // Ensure we analyze at least 10,000 paths + minIterations := 10000 + if b.N < minIterations { + b.N = minIterations + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + path := paths[i%len(paths)] + _, err := analyzer.AnalyzePath(path, identifier) + if err != nil { + b.Fatalf("Error analyzing path: %v", err) + } + } +} + +func BenchmarkAnalyzePathWithDifferentLengths(b *testing.B) { + pathLengths := []int{1, 3, 5, 10, 20, 50, 100} + + for _, length := range pathLengths { + b.Run(fmt.Sprintf("PathLength-%d", length), func(b *testing.B) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + paths := generateMixedPaths(10000, length) + identifier := "test" + + // Ensure we analyze at least 10,000 paths + minIterations := 10000 + if b.N < minIterations { + b.N = minIterations + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + path := paths[i%len(paths)] + _, err := analyzer.AnalyzePath(path, identifier) + if err != nil { + b.Fatalf("Error analyzing path: %v", err) + } + } + + }) + } +} + +func generateMixedPaths(count int, fixedLength int) []string { + paths := make([]string, count) + staticSegments := []string{"users", "profile", "settings", "api", "v1", "posts", "organizations", "departments", "employees", "projects", "tasks", "categories", "subcategories", "items", "articles"} + + for i := 0; i < count; i++ { + if fixedLength > 0 { + segments := make([]string, fixedLength) + for j := 0; j < fixedLength; j++ { + if rand.Float32() < 0.2 { // 20% chance of dynamic segment + prefix := staticSegments[rand.Intn(len(staticSegments))] + // Generate a value > 100 to ensure it's considered dynamic + dynamicValue := rand.Intn(10000) + 101 + segments[j] = fmt.Sprintf("%s_%d", prefix, dynamicValue) + } else { // 80% chance of static segment + segments[j] = staticSegments[rand.Intn(len(staticSegments))] + } + } + paths[i] = "/" + strings.Join(segments, "/") + } else { + // Use the original mixed path generation logic for variable length paths + switch rand.Intn(6) { + case 0: + paths[i] = "/users/profile/settings" + case 1: + paths[i] = fmt.Sprintf("/users/%d/profile", i%200) + case 2: + paths[i] = fmt.Sprintf("/api/v1/users/%d/posts/%d", i%200, i%150) + case 3: + paths[i] = fmt.Sprintf("/organizations/%d/departments/%d/employees/%d/projects/%d/tasks/%d", + i%100, i%50, i%1000, i%30, i%200) + case 4: + repeatedSegment := fmt.Sprintf("%d", i%150) + paths[i] = fmt.Sprintf("/categories/%s/subcategories/%s/items/%s", + repeatedSegment, repeatedSegment, repeatedSegment) + case 5: + paths[i] = fmt.Sprintf("/articles/%d/%s-%s-%s", + i%100, + generateRandomString(5), + generateRandomString(7), + generateRandomString(5)) + } + } + } + return paths +} + +// Helper function to generate random strings +func generateRandomString(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyz0123456789" + result := make([]byte, length) + for i := range result { + result[i] = charset[rand.Intn(len(charset))] + } + return string(result) +} diff --git a/pkg/registry/file/dynamicpathdetector/tests/coverage_test.go b/pkg/registry/file/dynamicpathdetector/tests/coverage_test.go new file mode 100644 index 000000000..e011569bd --- /dev/null +++ b/pkg/registry/file/dynamicpathdetector/tests/coverage_test.go @@ -0,0 +1,182 @@ +package dynamicpathdetectortests + +import ( + "fmt" + "testing" + + "github.com/kubescape/storage/pkg/registry/file/dynamicpathdetector" +) + +func TestNewPathAnalyzer(t *testing.T) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + if analyzer == nil { + t.Error("NewPathAnalyzer() returned nil") + } +} + +func TestAnalyzePath(t *testing.T) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + + testCases := []struct { + name string + path string + identifier string + expected string + }{ + {"Simple path", "/api/users/123", "api", "/api/users/123"}, + {"Multiple segments", "/api/users/123/posts/456", "api", "/api/users/123/posts/456"}, + {"Root path", "/api/", "api", "/api"}, + {"Empty path", "/api/", "api", "/api"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := analyzer.AnalyzePath(tc.path, tc.identifier) + if err != nil { + t.Errorf("AnalyzePath(%q, %q) returned an error: %v", tc.path, tc.identifier, err) + } + if result != tc.expected { + t.Errorf("AnalyzePath(%q, %q) = %q, want %q", tc.path, tc.identifier, result, tc.expected) + } + }) + } +} + +func TestDynamicSegments(t *testing.T) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + + // Create 99 different paths under the 'users' segment + for i := 0; i < 101; i++ { + path := fmt.Sprintf("/api/users/%d", i) + _, err := analyzer.AnalyzePath(path, "api") + if err != nil { + t.Errorf("AnalyzePath() returned an error: %v", err) + } + } + + result, err := analyzer.AnalyzePath("/api/users/101", "api") + if err != nil { + t.Errorf("AnalyzePath() returned an error: %v", err) + } + expected := "/api/users/" + if result != expected { + t.Errorf("AnalyzePath(\"/users/101\", \"api\") = %q, want %q", result, expected) + } + + // Test with one of the original IDs to ensure it's also marked as dynamic + result, err = analyzer.AnalyzePath("/api/users/50", "api") + if err != nil { + t.Errorf("AnalyzePath() returned an error: %v", err) + } + if result != expected { + t.Errorf("AnalyzePath(\"/users/50\", \"api\") = %q, want %q", result, expected) + } +} + +func TestMultipleDynamicSegments(t *testing.T) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + + // Create 99 different paths for both 'users' and 'posts' segments + for i := 0; i < 110; i++ { + path := fmt.Sprintf("/api/users/%d/posts/%d", i, i) + _, err := analyzer.AnalyzePath(path, "api") + if err != nil { + t.Errorf("AnalyzePath() returned an error: %v", err) + } + } + + // Test with the 100th unique user and post IDs (should trigger dynamic segments) + result, err := analyzer.AnalyzePath("/api/users/101/posts/1031", "api") + if err != nil { + t.Errorf("AnalyzePath() returned an error: %v", err) + } + expected := "/api/users//posts/" + if result != expected { + t.Errorf("AnalyzePath(\"/users/99/posts/99\", \"api\") = %q, want %q", result, expected) + } + +} + +func TestMixedStaticAndDynamicSegments(t *testing.T) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + + // Create 99 different paths for 'users' but keep 'posts' static + for i := 0; i < 101; i++ { + path := fmt.Sprintf("/api/users/%d/posts", i) + _, err := analyzer.AnalyzePath(path, "api") + if err != nil { + t.Errorf("AnalyzePath() returned an error: %v", err) + } + } + + // Test with the 100th unique user ID but same 'posts' segment (should trigger dynamic segment for users) + result, err := analyzer.AnalyzePath("/api/users/99/posts", "api") + if err != nil { + t.Errorf("AnalyzePath() returned an error: %v", err) + } + expected := "/api/users//posts" + if result != expected { + t.Errorf("AnalyzePath(\"/users/99/posts\", \"api\") = %q, want %q", result, expected) + } + +} + +func TestDifferentRootIdentifiers(t *testing.T) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + + // Analyze paths with different root identifiers + result1, _ := analyzer.AnalyzePath("/api/users/123", "api") + result2, _ := analyzer.AnalyzePath("/api/products/456", "store") + + if result1 != "/api/users/123" { + t.Errorf("AnalyzePath(\"/users/123\", \"api\") = %q, want \"/api/users/123\"", result1) + } + + if result2 != "/api/products/456" { + t.Errorf("AnalyzePath(\"/products/456\", \"store\") = %q, want \"/store/products/456\"", result2) + } +} + +func TestDynamicThreshold(t *testing.T) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + + for i := 0; i < 101; i++ { + path := fmt.Sprintf("/api/users/%d", i) + result, _ := analyzer.AnalyzePath(path, "api") + if result != fmt.Sprintf("/api/users/%d", i) { + t.Errorf("Path became dynamic before reaching 99 different paths") + } + } + + result, _ := analyzer.AnalyzePath("/api/users/991", "api") + if result != "/api/users/" { + t.Errorf("Path did not become dynamic after 99 different paths") + } +} + +func TestEdgeCases(t *testing.T) { + analyzer := dynamicpathdetector.NewPathAnalyzer() + + testCases := []struct { + name string + path string + identifier string + expected string + }{ + {"Path with multiple slashes", "//users///123////", "api", "/users/123"}, + {"Path with special characters", "/users/@johndoe/settings", "api", "/users/@johndoe/settings"}, + {"Very long path", "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p", "api", "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := analyzer.AnalyzePath(tc.path, tc.identifier) + if err != nil { + t.Errorf("AnalyzePath(%q, %q) returned an error: %v", tc.path, tc.identifier, err) + } + if result != tc.expected { + t.Errorf("AnalyzePath(%q, %q) = %q, want %q", tc.path, tc.identifier, result, tc.expected) + } + }) + } +} diff --git a/pkg/registry/file/dynamicpathdetector/types.go b/pkg/registry/file/dynamicpathdetector/types.go new file mode 100644 index 000000000..b080efa91 --- /dev/null +++ b/pkg/registry/file/dynamicpathdetector/types.go @@ -0,0 +1,20 @@ +package dynamicpathdetector + +const dynamicIdentifier string = "" + +const threshold = 100 + +type SegmentNode struct { + SegmentName string + Count int + Children map[string]*SegmentNode +} + +type PathAnalyzer struct { + RootNodes map[string]*SegmentNode +} + +func (sn *SegmentNode) IsNextDynamic() bool { + _, exists := sn.Children[dynamicIdentifier] + return exists +}