mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
feat: add cli package to load policy exceptions (#8508)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
parent
d4d5d751b1
commit
21e044eb1a
7 changed files with 1178 additions and 22 deletions
1
Makefile
1
Makefile
|
@ -539,6 +539,7 @@ codegen-cli-crds: codegen-crds-kyverno ## Copy generated CRDs to embed in the CL
|
||||||
@rm -rf cmd/cli/kubectl-kyverno/data/crds && mkdir -p cmd/cli/kubectl-kyverno/data/crds
|
@rm -rf cmd/cli/kubectl-kyverno/data/crds && mkdir -p cmd/cli/kubectl-kyverno/data/crds
|
||||||
@cp config/crds/kyverno.io_clusterpolicies.yaml cmd/cli/kubectl-kyverno/data/crds
|
@cp config/crds/kyverno.io_clusterpolicies.yaml cmd/cli/kubectl-kyverno/data/crds
|
||||||
@cp config/crds/kyverno.io_policies.yaml cmd/cli/kubectl-kyverno/data/crds
|
@cp config/crds/kyverno.io_policies.yaml cmd/cli/kubectl-kyverno/data/crds
|
||||||
|
@cp config/crds/kyverno.io_policyexceptions.yaml cmd/cli/kubectl-kyverno/data/crds
|
||||||
@cp cmd/cli/kubectl-kyverno/config/crds/* cmd/cli/kubectl-kyverno/data/crds
|
@cp cmd/cli/kubectl-kyverno/config/crds/* cmd/cli/kubectl-kyverno/data/crds
|
||||||
|
|
||||||
.PHONY: codegen-docs-all
|
.PHONY: codegen-docs-all
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
apiVersion: kyverno.io/v2alpha1
|
||||||
|
kind: PolicyException
|
||||||
|
metadata:
|
||||||
|
name: delta-exception
|
||||||
|
namespace: delta
|
||||||
|
spec:
|
||||||
|
exceptions:
|
||||||
|
- policyName: disallow-host-namespaces
|
||||||
|
ruleNames:
|
||||||
|
- host-namespaces
|
||||||
|
- autogen-host-namespaces
|
||||||
|
match:
|
||||||
|
any:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- Pod
|
||||||
|
- Deployment
|
||||||
|
namespaces:
|
||||||
|
- delta
|
||||||
|
names:
|
||||||
|
- important-tool*
|
||||||
|
---
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: Policy
|
||||||
|
metadata:
|
||||||
|
name: require-ns-purpose-label
|
||||||
|
namespace: test
|
||||||
|
spec:
|
||||||
|
validationFailureAction: Enforce
|
||||||
|
rules:
|
||||||
|
- name: require-ns-purpose-label
|
||||||
|
match:
|
||||||
|
any:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- Namespace
|
||||||
|
validate:
|
||||||
|
message: "You must have label 'purpose' with value 'production' set on all new namespaces."
|
||||||
|
pattern:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
purpose: production
|
21
cmd/cli/kubectl-kyverno/_testdata/exceptions/exception.yaml
Normal file
21
cmd/cli/kubectl-kyverno/_testdata/exceptions/exception.yaml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
apiVersion: kyverno.io/v2alpha1
|
||||||
|
kind: PolicyException
|
||||||
|
metadata:
|
||||||
|
name: delta-exception
|
||||||
|
namespace: delta
|
||||||
|
spec:
|
||||||
|
exceptions:
|
||||||
|
- policyName: disallow-host-namespaces
|
||||||
|
ruleNames:
|
||||||
|
- host-namespaces
|
||||||
|
- autogen-host-namespaces
|
||||||
|
match:
|
||||||
|
any:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- Pod
|
||||||
|
- Deployment
|
||||||
|
namespaces:
|
||||||
|
- delta
|
||||||
|
names:
|
||||||
|
- important-tool*
|
1018
cmd/cli/kubectl-kyverno/data/crds/kyverno.io_policyexceptions.yaml
Normal file
1018
cmd/cli/kubectl-kyverno/data/crds/kyverno.io_policyexceptions.yaml
Normal file
File diff suppressed because it is too large
Load diff
45
cmd/cli/kubectl-kyverno/exception/load.go
Normal file
45
cmd/cli/kubectl-kyverno/exception/load.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package exception
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
|
||||||
|
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
|
||||||
|
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/data"
|
||||||
|
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource/convert"
|
||||||
|
resourceloader "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource/loader"
|
||||||
|
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"sigs.k8s.io/kubectl-validate/pkg/openapiclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
factory, _ = resourceloader.New(openapiclient.NewComposite(openapiclient.NewLocalCRDFiles(data.Crds(), data.CrdsFolder)))
|
||||||
|
exceptionV1 = schema.GroupVersion(kyvernov2alpha1.GroupVersion).WithKind("PolicyException")
|
||||||
|
exceptionV2 = schema.GroupVersion(kyvernov2beta1.GroupVersion).WithKind("PolicyException")
|
||||||
|
)
|
||||||
|
|
||||||
|
func Load(content []byte) ([]*kyvernov2alpha1.PolicyException, error) {
|
||||||
|
documents, err := yamlutils.SplitDocuments(content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var exceptions []*kyvernov2alpha1.PolicyException
|
||||||
|
for _, document := range documents {
|
||||||
|
gvk, untyped, err := factory.Load(document)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch gvk {
|
||||||
|
case exceptionV1, exceptionV2:
|
||||||
|
exception, err := convert.To[kyvernov2alpha1.PolicyException](untyped)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
exceptions = append(exceptions, exception)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("policy exception type not supported %s", gvk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exceptions, nil
|
||||||
|
}
|
41
cmd/cli/kubectl-kyverno/exception/load_test.go
Normal file
41
cmd/cli/kubectl-kyverno/exception/load_test.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package exception
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Load(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
policies string
|
||||||
|
wantLoaded int
|
||||||
|
wantErr bool
|
||||||
|
}{{
|
||||||
|
name: "not a policy exception",
|
||||||
|
policies: "../_testdata/resources/namespace.yaml",
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "policy exception",
|
||||||
|
policies: "../_testdata/exceptions/exception.yaml",
|
||||||
|
wantLoaded: 1,
|
||||||
|
}, {
|
||||||
|
name: "policy exception and policy",
|
||||||
|
policies: "../_testdata/exceptions/exception-and-policy.yaml",
|
||||||
|
wantErr: true,
|
||||||
|
}}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
bytes, err := os.ReadFile(tt.policies)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if res, err := Load(bytes); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
} else if len(res) != tt.wantLoaded {
|
||||||
|
t.Errorf("Load() loaded amount = %v, wantLoaded %v", len(res), tt.wantLoaded)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,15 +42,9 @@ func TestNew(t *testing.T) {
|
||||||
}
|
}
|
||||||
}(),
|
}(),
|
||||||
}, {
|
}, {
|
||||||
name: "invalid local",
|
name: "invalid local",
|
||||||
client: openapiclient.NewLocalSchemaFiles(data.Crds(), "blam"),
|
client: openapiclient.NewLocalCRDFiles(data.Crds(), "blam"),
|
||||||
want: func() Loader {
|
wantErr: true,
|
||||||
validator, err := validator.New(openapiclient.NewLocalSchemaFiles(data.Crds(), "blam"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
return &loader{
|
|
||||||
validator: validator,
|
|
||||||
}
|
|
||||||
}(),
|
|
||||||
}, {
|
}, {
|
||||||
name: "composite - no clients",
|
name: "composite - no clients",
|
||||||
client: openapiclient.NewComposite(),
|
client: openapiclient.NewComposite(),
|
||||||
|
@ -70,15 +64,9 @@ func TestNew(t *testing.T) {
|
||||||
client: openapiclient.NewComposite(openapiclient.NewHardcodedBuiltins("1.27"), errClient{}),
|
client: openapiclient.NewComposite(openapiclient.NewHardcodedBuiltins("1.27"), errClient{}),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
}, {
|
}, {
|
||||||
name: "composite - invalid local",
|
name: "composite - invalid local",
|
||||||
client: openapiclient.NewComposite(openapiclient.NewLocalSchemaFiles(data.Crds(), "blam")),
|
client: openapiclient.NewComposite(openapiclient.NewLocalCRDFiles(data.Crds(), "blam")),
|
||||||
want: func() Loader {
|
wantErr: true,
|
||||||
validator, err := validator.New(openapiclient.NewComposite(openapiclient.NewLocalSchemaFiles(data.Crds(), "blam")))
|
|
||||||
require.NoError(t, err)
|
|
||||||
return &loader{
|
|
||||||
validator: validator,
|
|
||||||
}
|
|
||||||
}(),
|
|
||||||
}}
|
}}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -123,16 +111,16 @@ func Test_loader_Load(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{{
|
}{{
|
||||||
name: "nil",
|
name: "nil",
|
||||||
loader: newLoader(openapiclient.NewLocalSchemaFiles(data.Crds(), "schemas")),
|
loader: newLoader(openapiclient.NewLocalCRDFiles(data.Crds(), "crds")),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
}, {
|
}, {
|
||||||
name: "empty GVK",
|
name: "empty GVK",
|
||||||
loader: newLoader(openapiclient.NewLocalSchemaFiles(data.Crds(), "schemas")),
|
loader: newLoader(openapiclient.NewLocalCRDFiles(data.Crds(), "crds")),
|
||||||
document: []byte(`foo: bar`),
|
document: []byte(`foo: bar`),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
}, {
|
}, {
|
||||||
name: "not yaml",
|
name: "not yaml",
|
||||||
loader: newLoader(openapiclient.NewLocalSchemaFiles(data.Crds(), "schemas")),
|
loader: newLoader(openapiclient.NewLocalCRDFiles(data.Crds(), "crds")),
|
||||||
document: []byte(`
|
document: []byte(`
|
||||||
foo
|
foo
|
||||||
bar
|
bar
|
||||||
|
@ -140,7 +128,7 @@ func Test_loader_Load(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
}, {
|
}, {
|
||||||
name: "unknown GVK",
|
name: "unknown GVK",
|
||||||
loader: newLoader(openapiclient.NewLocalSchemaFiles(data.Crds(), "schemas")),
|
loader: newLoader(openapiclient.NewLocalCRDFiles(data.Crds(), "crds")),
|
||||||
document: loadFile("../../_testdata/resources/namespace.yaml"),
|
document: loadFile("../../_testdata/resources/namespace.yaml"),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
}, {
|
}, {
|
||||||
|
|
Loading…
Reference in a new issue