Skip to content

Commit ee76e8d

Browse files
authored
Add kubeconform based manifest validation (#11)
1 parent 96a014f commit ee76e8d

26 files changed

Lines changed: 1762 additions & 124 deletions

File tree

CREDITS

Lines changed: 1199 additions & 71 deletions
Large diffs are not rendered by default.

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.23 AS builder
1+
FROM golang:1.24 AS builder
22
ARG GOOS
33
ARG GOARCH
44
ARG APP_NAME
@@ -12,7 +12,7 @@ RUN --mount=type=cache,target=/go/pkg/mod/ \
1212

1313
# Lint
1414
FROM builder AS lint-internal
15-
COPY --from=golangci/golangci-lint:v1.63 /usr/bin/golangci-lint /usr/bin/golangci-lint
15+
COPY --from=golangci/golangci-lint:v1.64 /usr/bin/golangci-lint /usr/bin/golangci-lint
1616
RUN --mount=type=cache,target=/go/pkg/mod/ \
1717
--mount=type=bind,target=. \
1818
golangci-lint run

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ policies:
8686
resources:
8787
- <path/to/resource.yaml>
8888
- <path/to/resource.yaml>
89+
schemaLocations: # Optional: For Custom Resources
90+
- <url/to/crd/schema.json>
91+
- <path/to/crd/schema.json>
8992
vapTestSuites:
9093
- policy: <name> # ValidatingAdmissionPolicy's name
9194
tests:
@@ -153,6 +156,17 @@ kaptest run <path/to/test_manifest.yaml> ...
153156

154157
You can run test cases of multiple YAML files at once and display the test results together.
155158

159+
#### Resource Manifest Validation
160+
161+
kaptest incorporates [kubeconform](https://github.com/yannh/kubeconform) based manifest validation.
162+
All resources including policies need to follow schema.
163+
164+
For custom resources, users need to define `schemaLocations` in a test policy to get CRD schema.
165+
The schema specification follows kubeconform's one and users can utlize schemas for it.
166+
The example is described in [internal/tester/testdata/vap-custom-resources.test/kaptest.yaml](./internal/tester/testdata/vap-custom-resources.test/kaptest.yaml).
167+
168+
`kaptest run <test_manifest.yaml> --validate-resource-manifests=false` disables the validation.
169+
156170
### Operation Type
157171

158172
You can describe the cases for CREATE, UPDATE, and DELETE operations based on whether object and oldObject are specified. These are determined by the following conditions:

go.mod

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
module github.com/pfnet/kaptest
22

3-
go 1.23.6
3+
go 1.24
44

55
require (
6+
github.com/google/go-cmp v0.6.0
67
github.com/spf13/cobra v1.8.1
8+
github.com/yannh/kubeconform v0.7.0
79
gopkg.in/yaml.v2 v2.4.0
810
k8s.io/api v0.32.1
911
k8s.io/apimachinery v0.32.1
1012
k8s.io/apiserver v0.32.1
1113
k8s.io/cli-runtime v0.31.0
14+
k8s.io/client-go v0.32.1
1215
k8s.io/utils v0.0.0-20241210054802-24370beab758
1316
)
1417

@@ -36,10 +39,11 @@ require (
3639
github.com/golang/protobuf v1.5.4 // indirect
3740
github.com/google/cel-go v0.23.2 // indirect
3841
github.com/google/gnostic-models v0.6.9 // indirect
39-
github.com/google/go-cmp v0.6.0 // indirect
4042
github.com/google/gofuzz v1.2.0 // indirect
4143
github.com/google/uuid v1.6.0 // indirect
4244
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
45+
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
46+
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
4347
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4448
github.com/josharian/intern v1.0.0 // indirect
4549
github.com/json-iterator/go v1.1.12 // indirect
@@ -55,6 +59,7 @@ require (
5559
github.com/prometheus/client_model v0.6.1 // indirect
5660
github.com/prometheus/common v0.62.0 // indirect
5761
github.com/prometheus/procfs v0.15.1 // indirect
62+
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
5863
github.com/spf13/pflag v1.0.6 // indirect
5964
github.com/stoewer/go-strcase v1.3.0 // indirect
6065
github.com/x448/float16 v0.8.4 // indirect
@@ -70,10 +75,10 @@ require (
7075
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 // indirect
7176
golang.org/x/net v0.34.0 // indirect
7277
golang.org/x/oauth2 v0.26.0 // indirect
73-
golang.org/x/sync v0.11.0 // indirect
78+
golang.org/x/sync v0.14.0 // indirect
7479
golang.org/x/sys v0.30.0 // indirect
7580
golang.org/x/term v0.29.0 // indirect
76-
golang.org/x/text v0.22.0 // indirect
81+
golang.org/x/text v0.25.0 // indirect
7782
golang.org/x/time v0.10.0 // indirect
7883
google.golang.org/genproto/googleapis/api v0.0.0-20250207221924-e9438ea467c6 // indirect
7984
google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect
@@ -82,7 +87,6 @@ require (
8287
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
8388
gopkg.in/inf.v0 v0.9.1 // indirect
8489
gopkg.in/yaml.v3 v3.0.1 // indirect
85-
k8s.io/client-go v0.32.1 // indirect
8690
k8s.io/component-base v0.32.1 // indirect
8791
k8s.io/klog/v2 v2.130.1 // indirect
8892
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect

go.sum

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
2121
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2222
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
2323
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24+
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
25+
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
2426
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
2527
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
28+
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
29+
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
2630
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
2731
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
2832
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
@@ -60,6 +64,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
6064
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
6165
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
6266
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
67+
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
68+
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
69+
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
70+
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
71+
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
72+
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
6373
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
6474
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
6575
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -80,6 +90,10 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn
8090
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
8191
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
8292
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
93+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
94+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
95+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
96+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
8397
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
8498
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
8599
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -109,6 +123,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG
109123
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
110124
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
111125
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
126+
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
127+
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
112128
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
113129
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
114130
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -127,6 +143,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
127143
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
128144
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
129145
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
146+
github.com/yannh/kubeconform v0.7.0 h1:ZFfniR8VChrWQxaxTUGnNrxw8RIDkjVBrjdhXSamwjw=
147+
github.com/yannh/kubeconform v0.7.0/go.mod h1:oHO1wjM16sTRW6s41HJUox+tD69qOTE5ZVQ9HeqX+xM=
130148
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
131149
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
132150
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
@@ -169,8 +187,8 @@ golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht
169187
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
170188
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
171189
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
172-
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
173-
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
190+
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
191+
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
174192
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
175193
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
176194
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -181,8 +199,8 @@ golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
181199
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
182200
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
183201
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
184-
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
185-
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
202+
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
203+
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
186204
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
187205
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
188206
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

internal/cmd/run.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,33 @@ package cmd
1818

1919
import (
2020
"fmt"
21+
"os"
22+
"path/filepath"
2123

2224
"github.com/pfnet/kaptest/internal/tester"
2325
"github.com/spf13/cobra"
2426
)
2527

2628
func newRunCmd(cfg *tester.CmdConfig) *cobra.Command {
27-
return &cobra.Command{
29+
testerCfg := tester.TesterCmdConfig{
30+
CmdConfig: *cfg,
31+
}
32+
cmd := &cobra.Command{
2833
Use: "run [path to test manifest]...",
2934
Short: "Run the tests of ValidatingAdmissionPolicy and MutatingAdmissionPolicy",
3035
RunE: func(cmd *cobra.Command, args []string) error {
3136
if len(args) == 0 {
3237
return fmt.Errorf("path is required")
3338
}
34-
return tester.Run(*cfg, args)
39+
return tester.Run(testerCfg, args)
3540
},
3641
}
42+
homeDir, err := os.UserHomeDir()
43+
if err != nil {
44+
panic(err)
45+
}
46+
cmd.Flags().BoolVarP(&testerCfg.ValidateResourceManifest, "validate-resource-manifests", "", true, "Validating the resource manifests according to the schema")
47+
cmd.Flags().StringVarP(&testerCfg.SchemaCache, "schema-cache", "", filepath.Join(homeDir, ".cache/kaptest/schema"), "Path to cache schemas used in resource manifest validation")
48+
49+
return cmd
3750
}

internal/cmd/version.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"fmt"
55

6+
"github.com/pfnet/kaptest/internal/util"
67
"github.com/spf13/cobra"
78
)
89

@@ -18,6 +19,7 @@ func newVersionCmd() *cobra.Command {
1819
RunE: func(cmd *cobra.Command, args []string) error {
1920
fmt.Printf("Version: %s\n", version)
2021
fmt.Printf("Commit: %s\n", commit)
22+
fmt.Printf("Supported Kubernetes Version: %s\n", util.GetSupportedKubernetesVersion())
2123
return nil
2224
},
2325
}

internal/tester/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func createRootManifest(targetFilePath string) error {
8080
return fmt.Errorf("file already exists: %s", p)
8181
}
8282

83-
loader := NewResourceLoader()
83+
loader := NewResourceLoader(nil)
8484
loader.LoadPolicies([]string{targetFilePath})
8585
slog.Debug(fmt.Sprintf("test target ValidatingAdmissionPolicies: %v", maps.Keys(loader.Vaps)))
8686
slog.Debug(fmt.Sprintf("test target MutatingAdmissionPolicies: %v", maps.Keys(loader.Maps)))

internal/tester/loader.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import (
2424
"log/slog"
2525
"os"
2626

27+
"github.com/yannh/kubeconform/pkg/resource"
28+
"github.com/yannh/kubeconform/pkg/validator"
29+
"gopkg.in/yaml.v2"
2730
v1 "k8s.io/api/admissionregistration/v1"
2831
"k8s.io/api/admissionregistration/v1alpha1"
2932
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -36,13 +39,15 @@ type ResourceLoader struct {
3639
Vaps map[string]*v1.ValidatingAdmissionPolicy
3740
Maps map[string]*v1alpha1.MutatingAdmissionPolicy
3841
Resources map[NameWithGVK]*unstructured.Unstructured
42+
validator validator.Validator
3943
}
4044

41-
func NewResourceLoader() *ResourceLoader {
45+
func NewResourceLoader(validator validator.Validator) *ResourceLoader {
4246
return &ResourceLoader{
4347
Vaps: map[string]*v1.ValidatingAdmissionPolicy{},
4448
Maps: map[string]*v1alpha1.MutatingAdmissionPolicy{},
4549
Resources: map[NameWithGVK]*unstructured.Unstructured{},
50+
validator: validator,
4651
}
4752
}
4853

@@ -75,6 +80,14 @@ func (r *ResourceLoader) LoadPolicies(paths []string) {
7580
continue
7681
}
7782

83+
if r.validator != nil {
84+
res := r.validator.ValidateResource(resource.Resource{Bytes: b})
85+
if res.Err != nil || res.Status != validator.Valid {
86+
slog.Error("Invalid policy exists")
87+
continue
88+
}
89+
}
90+
7891
obj, gvk, err := decoder.Decode(b, nil, nil)
7992
if err != nil {
8093
slog.Warn("failed to decode policy", "error", err)
@@ -115,6 +128,7 @@ func (r *ResourceLoader) LoadResources(paths []string) {
115128
slog.Error("read yaml file", "error", err)
116129
continue
117130
}
131+
118132
decoder := kyaml.NewYAMLToJSONDecoder(yamlFile)
119133
for {
120134
var obj map[string]any
@@ -126,6 +140,22 @@ func (r *ResourceLoader) LoadResources(paths []string) {
126140
continue
127141
}
128142
unstructuredObj := &unstructured.Unstructured{Object: obj}
143+
144+
// if resource manifest validation is enabled, check whether the resource manifest follows a schema.
145+
if r.validator != nil {
146+
// TODO: avoid re-marshal
147+
objYamlBytes, err := yaml.Marshal(obj)
148+
if err != nil {
149+
slog.Warn("failed to marshal object into yaml", "error", err)
150+
continue
151+
}
152+
res := r.validator.ValidateResource(resource.Resource{Bytes: objYamlBytes})
153+
if res.Err != nil || res.Status != validator.Valid {
154+
slog.Error("A resource is invalid", "obj", unstructuredObj.GetName(), "status", res.Status, "error", res.Err)
155+
continue
156+
}
157+
}
158+
129159
ngvk := NewNameWithGVKFromObj(unstructuredObj)
130160
r.Resources[ngvk] = unstructuredObj
131161
}

internal/tester/manifest.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ import (
2727

2828
// TestManifests is a struct to represent the whole test manifest file.
2929
type TestManifests struct {
30-
Policies []string `yaml:"policies,omitempty"`
31-
Resources []string `yaml:"resources,omitempty"`
32-
VapTestSuites []TestsForSingleVapPolicy `yaml:"vapTestSuites,omitempty"`
33-
MapTestSuites []TestsForSingleMapPolicy `yaml:"mapTestSuites,omitempty"`
30+
Policies []string `yaml:"policies,omitempty"`
31+
Resources []string `yaml:"resources,omitempty"`
32+
SchemaLocations []string `yaml:"schemaLocations,omitempty"` // used for resource manifest validation
33+
VapTestSuites []TestsForSingleVapPolicy `yaml:"vapTestSuites,omitempty"`
34+
MapTestSuites []TestsForSingleMapPolicy `yaml:"mapTestSuites,omitempty"`
3435
}
3536

3637
func (t TestManifests) IsValid() (bool, string) {

0 commit comments

Comments
 (0)