diff --git a/Dockerfile b/Dockerfile index f24fb509..a905ada6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23.1-alpine AS build +FROM golang:1.23.4-alpine AS build ARG KONSTRAINT_VER WORKDIR /go/src/github.com/plexsystems/konstraint diff --git a/docs/cli/konstraint_create.md b/docs/cli/konstraint_create.md index 000ab8cf..5ab188d2 100644 --- a/docs/cli/konstraint_create.md +++ b/docs/cli/konstraint_create.md @@ -22,12 +22,14 @@ Create constraints with the Gatekeeper enforcement action set to dryrun ### Options ``` - --constraint-template-version string Set the version of ConstraintTemplates (default "v1beta1") - -d, --dryrun Sets the enforcement action of the constraints to dryrun, overriding the @enforcement tag - -h, --help help for create - -o, --output string Specify an output directory for the Gatekeeper resources - --partial-constraints Generate partial Constraints for policies with parameters - --skip-constraints Skip generation of constraints + --constraint-custom-template-file string Path to a custom template file to generate constraints + --constraint-template-custom-template-file string Path to a custom template file to generate constraint templates + --constraint-template-version string Set the version of ConstraintTemplates (default "v1beta1") + -d, --dryrun Sets the enforcement action of the constraints to dryrun, overriding the @enforcement tag + -h, --help help for create + -o, --output string Specify an output directory for the Gatekeeper resources + --partial-constraints Generate partial Constraints for policies with parameters + --skip-constraints Skip generation of constraints ``` ### SEE ALSO diff --git a/docs/constraint_creation.md b/docs/constraint_creation.md index dbf8712c..81cb44c4 100644 --- a/docs/constraint_creation.md +++ b/docs/constraint_creation.md @@ -103,6 +103,12 @@ violation[{"msg": msg}] { } ``` +### Custom templates for Constraint and/or ConstraintTemplate resources + +In some cases there might be the need to further customize the rendered Constraint and ConstraintTemplates. This is particularly helpful, if you want to create e.g. template for Helm charts, where certain values are additional fields to be rendered through Helm. +You can provide custom templates through the `--constraint-template-custom-template-file` and `--constraint-custom-template-file` command line flags. + + ### Skipping generation of the Constraint and/or ConstraintTemplate resource In some scenarios, you may wish for Konstraint to skip the generation of the `Constraint` resource for a policy and manage that externally. To do so, add the `skipConstraint: true` annotation in the custom metadata section. diff --git a/go.mod b/go.mod index b0b963fb..b6e2e755 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,23 @@ module github.com/plexsystems/konstraint -go 1.23.0 +go 1.23.4 require ( + github.com/go-sprout/sprout v1.0.0-rc.3 github.com/open-policy-agent/frameworks/constraint v0.0.0-20220218180203-c2a0d8cdf85a github.com/open-policy-agent/opa v0.69.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 - golang.org/x/text v0.18.0 + golang.org/x/text v0.21.0 k8s.io/apiextensions-apiserver v0.31.1 k8s.io/apimachinery v0.31.1 sigs.k8s.io/yaml v1.4.0 ) require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect @@ -45,7 +48,9 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -59,7 +64,7 @@ require ( github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -67,12 +72,13 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect golang.org/x/time v0.6.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect diff --git a/go.sum b/go.sum index b621a3d3..c6659dc2 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= @@ -53,6 +57,8 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-sprout/sprout v1.0.0-rc.3 h1:VsVYDglX/U7JqFU21AmAjY0PJ4wtf6/vIuAMjkQoW7I= +github.com/go-sprout/sprout v1.0.0-rc.3/go.mod h1:zDlFf3uJtlr9w0LkhOfuAeaqNzziQ/1XQWoiSm6cxro= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -115,8 +121,12 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -166,8 +176,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -179,6 +189,7 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -187,8 +198,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -234,6 +246,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -249,20 +263,20 @@ golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht 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= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/commands/constraint_template.tpl b/internal/commands/constraint_template.tpl new file mode 100644 index 00000000..fb7e4d62 --- /dev/null +++ b/internal/commands/constraint_template.tpl @@ -0,0 +1,20 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: {{ .Kind }} +metadata: + {{- if .Annotations }} + annotations: {{- .Annotations | toIndentYAML 2 | nindent 4 }} + {{- end }} + {{- if .Labels }} + labels: {{ .Labels | toIndentYAML 2 | nindent 4 }} + {{- end }} + name: {{ .Name }} +spec: + {{- if .Matchers }} + match: {{- .GetAnnotation "matchers" | toIndentYAML 2 | nindent 4 }} + {{- end }} + {{- if ne .Enforcement "deny" }} + enforcementAction: {{ .Enforcement }} + {{- end -}} + {{- if .AnnotationParameters }} + parameters: {{- .AnnotationParameters | toIndentYAML 2 | nindent 4 }} + {{- end }} diff --git a/internal/commands/constrainttemplate_template.tpl b/internal/commands/constrainttemplate_template.tpl new file mode 100644 index 00000000..823222ee --- /dev/null +++ b/internal/commands/constrainttemplate_template.tpl @@ -0,0 +1,20 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: {{ .Name }} +spec: + crd: + spec: + names: + kind: {{ .Kind }} + {{- if .AnnotationParameters }} + validation: + openAPIV3Schema: + properties: {{- .AnnotationParameters | toJSON | fromJSON | toIndentYAML 2 | nindent 12 }} + {{- end }} + targets: + - libs: {{- range .Dependencies }} + - |- {{- . | nindent 6 -}} + {{ end }} + rego: |- {{- .Source | nindent 6 }} + target: admission.k8s.gatekeeper.sh diff --git a/internal/commands/create.go b/internal/commands/create.go index 334dfb1f..71638584 100644 --- a/internal/commands/create.go +++ b/internal/commands/create.go @@ -1,13 +1,16 @@ package commands import ( + "bytes" "encoding/json" "fmt" "os" "path/filepath" + "text/template" "github.com/plexsystems/konstraint/internal/rego" + "github.com/go-sprout/sprout/sprigin" v1 "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1" "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1beta1" log "github.com/sirupsen/logrus" @@ -50,10 +53,21 @@ Create constraints with the Gatekeeper enforcement action set to dryrun if err := viper.BindPFlag("constraint-template-version", cmd.PersistentFlags().Lookup("constraint-template-version")); err != nil { return fmt.Errorf("bind constraint-template-version flag: %w", err) } + if err := viper.BindPFlag("constraint-template-custom-template-file", cmd.PersistentFlags().Lookup("constraint-template-custom-template-file")); err != nil { + return fmt.Errorf("bind constraint-template-custom-template-file flag: %w", err) + } + if err := viper.BindPFlag("constraint-custom-template-file", cmd.PersistentFlags().Lookup("constraint-custom-template-file")); err != nil { + return fmt.Errorf("bind constraint-custom-template-file flag: %w", err) + } + if err := viper.BindPFlag("partial-constraints", cmd.PersistentFlags().Lookup("partial-constraints")); err != nil { return fmt.Errorf("bind partial-constraints flag: %w", err) } + if cmd.PersistentFlags().Lookup("constraint-template-custom-template-file").Changed && cmd.PersistentFlags().Lookup("constraint-template-version").Changed { + return fmt.Errorf("need to set either constraint-template-custom-template-file or constraint-template-version") + } + path := "." if len(args) > 0 { path = args[0] @@ -68,6 +82,8 @@ Create constraints with the Gatekeeper enforcement action set to dryrun cmd.PersistentFlags().Bool("skip-constraints", false, "Skip generation of constraints") cmd.PersistentFlags().String("constraint-template-version", "v1beta1", "Set the version of ConstraintTemplates") cmd.PersistentFlags().Bool("partial-constraints", false, "Generate partial Constraints for policies with parameters") + cmd.PersistentFlags().String("constraint-template-custom-template-file", "", "Path to a custom template file to generate constraint templates") + cmd.PersistentFlags().String("constraint-custom-template-file", "", "Path to a custom template file to generate constraints") return &cmd } @@ -107,21 +123,36 @@ func runCreateCommand(path string) error { } constraintTemplateVersion := viper.GetString("constraint-template-version") + constraintTemplateCustomTemplateFile := viper.GetString("constraint-template-custom-template-file") + var constraintTemplate any - switch constraintTemplateVersion { - case "v1": - constraintTemplate = getConstraintTemplatev1(violation, logger) - case "v1beta1": - constraintTemplate = getConstraintTemplatev1beta1(violation, logger) - default: - return fmt.Errorf("unsupported API version for constrainttemplate: %s", constraintTemplateVersion) - } + var constraintTemplateBytes []byte - constraintTemplateBytes, err := yaml.Marshal(constraintTemplate) - if err != nil { - return fmt.Errorf("marshal constrainttemplate: %w", err) - } + if constraintTemplateCustomTemplateFile != "" { + customTemplate, err := os.ReadFile(constraintTemplateCustomTemplateFile) + if err != nil { + return fmt.Errorf("unable to open/read template file: %w", err) + } + constraintTemplateBytes, err = renderTemplate(violation, customTemplate, logger) + if err != nil { + return fmt.Errorf("unable to render custom template: %w", err) + } + } else { + switch constraintTemplateVersion { + case "v1": + constraintTemplate = getConstraintTemplatev1(violation, logger) + case "v1beta1": + constraintTemplate = getConstraintTemplatev1beta1(violation, logger) + default: + return fmt.Errorf("unsupported API version for constrainttemplate: %s", constraintTemplateVersion) + } + constraintTemplateBytes, err = yaml.Marshal(constraintTemplate) + if err != nil { + return fmt.Errorf("marshal constrainttemplate: %w", err) + } + + } if err := os.WriteFile(filepath.Join(outputDir, templateFileName), constraintTemplateBytes, 0644); err != nil { return fmt.Errorf("writing template: %w", err) } @@ -137,16 +168,28 @@ func runCreateCommand(path string) error { continue } - constraint, err := getConstraint(violation, logger) - if err != nil { - return fmt.Errorf("get constraint: %w", err) - } + constraintCustomTemplateFile := viper.GetString("constraint-custom-template-file") + var constraintBytes []byte + if constraintCustomTemplateFile != "" { + customTemplate, err := os.ReadFile(constraintCustomTemplateFile) + if err != nil { + return fmt.Errorf("unable to open/read template file: %w", err) + } + constraintBytes, err = renderTemplate(violation, customTemplate, logger) + if err != nil { + return fmt.Errorf("unable to render custom constraint: %w", err) + } + } else { + constraint, err := getConstraint(violation, logger) + if err != nil { + return fmt.Errorf("get constraint: %w", err) + } - constraintBytes, err := yaml.Marshal(constraint) - if err != nil { - return fmt.Errorf("marshal constraint: %w", err) + constraintBytes, err = yaml.Marshal(constraint) + if err != nil { + return fmt.Errorf("marshal constraint: %w", err) + } } - if err := os.WriteFile(filepath.Join(outputDir, constraintFileName), constraintBytes, 0644); err != nil { return fmt.Errorf("writing constraint: %w", err) } @@ -157,6 +200,20 @@ func runCreateCommand(path string) error { return nil } +func renderTemplate(violation rego.Rego, appliedTemplate []byte, _ *log.Entry) ([]byte, error) { + t, err := template.New("template").Funcs(sprigin.FuncMap()).Parse(string(appliedTemplate)) + if err != nil { + return nil, fmt.Errorf("parsing template: %w", err) + } + buf := new(bytes.Buffer) + + if err := t.Execute(buf, violation); err != nil { + return nil, fmt.Errorf("executing template: %w", err) + } + + return buf.Bytes(), nil +} + func getConstraintTemplatev1(violation rego.Rego, logger *log.Entry) *v1.ConstraintTemplate { constraintTemplate := v1.ConstraintTemplate{ TypeMeta: metav1.TypeMeta{ @@ -332,8 +389,8 @@ func getConstraint(violation rego.Rego, logger *log.Entry) (*unstructured.Unstru } } - metadataMatchers, ok := violation.GetAnnotation("matchers") - if ok { + metadataMatchers, err := violation.GetAnnotation("matchers") + if err == nil { if len(matchers.KindMatchers) > 0 || len(matchers.MatchLabelsMatcher) > 0 || len(matchers.MatchExpressionsMatcher) > 0 || diff --git a/internal/commands/document.go b/internal/commands/document.go index f76b01e5..e3e98362 100644 --- a/internal/commands/document.go +++ b/internal/commands/document.go @@ -9,6 +9,7 @@ import ( "strings" "text/template" + "github.com/go-sprout/sprout/sprigin" "github.com/plexsystems/konstraint/internal/rego" log "github.com/sirupsen/logrus" @@ -111,7 +112,7 @@ func runDocCommand(path string) error { appliedTemplate = string(b) } - t, err := template.New("docs").Parse(appliedTemplate) + t, err := template.New("docs").Funcs(sprigin.FuncMap()).Parse(appliedTemplate) if err != nil { return fmt.Errorf("parsing template: %w", err) diff --git a/internal/rego/rego.go b/internal/rego/rego.go index e9c71966..018764cb 100644 --- a/internal/rego/rego.go +++ b/internal/rego/rego.go @@ -181,18 +181,21 @@ func (r Rego) AnnotationParameters() map[string]apiextensionsv1.JSONSchemaProps return r.annoParameters } -func (r Rego) GetAnnotation(name string) (any, bool) { +func (r Rego) GetAnnotation(name string) (any, error) { if r.annotations == nil { - return nil, false + return nil, fmt.Errorf("No annotations set") } switch name { case "title": - return r.annotations.Title, true + return r.annotations.Title, nil case "description": - return r.annotations.Description, true + return r.annotations.Description, nil default: - v, ok := r.annotations.Custom[name] - return v, ok + if v, ok := r.annotations.Custom[name]; ok { + return v, nil + } else { + return nil, fmt.Errorf("Couldn't lookup %s in annotations", name) + } } }