1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 07:26:55 +00:00

refactor: introduce abstract client interface in engine (#7377)

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-06-10 11:20:34 +02:00 committed by GitHub
parent 123ba5f9d8
commit 42657f672f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 247 additions and 104 deletions

View file

@ -21,6 +21,7 @@ import (
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/adapters"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
@ -854,7 +855,7 @@ func initializeMockController(objects []runtime.Object) (*generate.GenerateContr
cfg,
config.NewDefaultMetricsConfiguration(),
jmespath.New(cfg),
client,
adapters.Client(client),
nil,
store.ContextLoaderFactory(nil),
nil,

View file

@ -11,6 +11,7 @@ import (
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/adapters"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/registryclient"
@ -114,7 +115,7 @@ OuterLoop:
cfg,
config.NewDefaultMetricsConfiguration(),
jmespath.New(cfg),
c.Client,
adapters.Client(c.Client),
registryclient.NewOrDie(),
store.ContextLoaderFactory(nil),
nil,

View file

@ -5,7 +5,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
@ -39,7 +38,7 @@ type mockContextLoader struct {
func (l *mockContextLoader) Load(
ctx context.Context,
jp jmespath.Interface,
client dclient.Interface,
client engineapi.Client,
_ registryclient.Client,
contextEntries []kyvernov1.ContextEntry,
jsonContext enginecontext.Interface,

View file

@ -11,6 +11,7 @@ import (
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/adapters"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/context/resolvers"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
@ -37,7 +38,7 @@ func NewEngine(
configuration,
metricsConfiguration,
jp,
client,
adapters.Client(client),
rclient,
engineapi.DefaultContextLoaderFactory(configMapResolver),
exceptionsSelector,

View file

@ -0,0 +1,114 @@
package dclient
import (
"context"
"github.com/kyverno/kyverno/pkg/utils/wildcard"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type Resource struct {
Group string
Version string
Resource string
SubResource string
Unstructured unstructured.Unstructured
}
func GetResources(c Interface, group, version, kind, subresource, namespace, name string) ([]Resource, error) {
var resources []Resource
gvrss, err := c.Discovery().FindResources(group, version, kind, subresource)
if err != nil {
return nil, err
}
for gvrs := range gvrss {
dyn := c.GetDynamicInterface().Resource(gvrs.GroupVersionResource())
var sub []string
if gvrs.SubResource != "" {
sub = []string{gvrs.SubResource}
}
// we can use `GET` directly
if namespace != "" && name != "" && !wildcard.ContainsWildcard(namespace) && !wildcard.ContainsWildcard(name) {
var obj *unstructured.Unstructured
var err error
obj, err = dyn.Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}, sub...)
if err != nil {
return nil, err
}
resources = append(resources, Resource{
Group: gvrs.Group,
Version: gvrs.Version,
Resource: gvrs.Resource,
SubResource: gvrs.SubResource,
Unstructured: *obj,
})
} else {
// we can use `LIST`
if gvrs.SubResource == "" {
list, err := dyn.List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, obj := range list.Items {
if match(namespace, name, obj.GetNamespace(), obj.GetName()) {
resources = append(resources, Resource{
Group: gvrs.Group,
Version: gvrs.Version,
Resource: gvrs.Resource,
SubResource: gvrs.SubResource,
Unstructured: obj,
})
}
}
} else {
// we need to use `LIST` / `GET`
list, err := dyn.List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
var parentObjects []unstructured.Unstructured
for _, obj := range list.Items {
if match(namespace, name, obj.GetNamespace(), obj.GetName()) {
parentObjects = append(parentObjects, obj)
}
}
for _, parentObject := range parentObjects {
var obj *unstructured.Unstructured
var err error
if parentObject.GetNamespace() == "" {
obj, err = dyn.Get(context.TODO(), name, metav1.GetOptions{}, sub...)
} else {
obj, err = dyn.Namespace(parentObject.GetNamespace()).Get(context.TODO(), name, metav1.GetOptions{}, sub...)
}
if err != nil {
return nil, err
}
resources = append(resources, Resource{
Group: gvrs.Group,
Version: gvrs.Version,
Resource: gvrs.Resource,
SubResource: gvrs.SubResource,
Unstructured: *obj,
})
}
}
}
}
return resources, nil
}
func match(namespacePattern, namePattern, namespace, name string) bool {
if namespacePattern == "" && namePattern == "" {
return true
} else if namespacePattern == "" {
if wildcard.Match(namePattern, name) {
return true
}
} else if wildcard.Match(namespacePattern, namespace) {
if namePattern == "" || wildcard.Match(namePattern, name) {
return true
}
}
return false
}

View file

@ -0,0 +1,54 @@
package adapters
import (
"context"
"io"
"github.com/kyverno/kyverno/pkg/auth"
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type dclientAdapter struct {
client dclient.Interface
}
func Client(client dclient.Interface) engineapi.Client {
return &dclientAdapter{client}
}
func (a *dclientAdapter) RawAbsPath(ctx context.Context, path, method string, dataReader io.Reader) ([]byte, error) {
return a.client.RawAbsPath(ctx, path, method, dataReader)
}
func (a *dclientAdapter) GetResources(group, version, kind, subresource, namespace, name string) ([]engineapi.Resource, error) {
resources, err := dclient.GetResources(a.client, group, version, kind, subresource, namespace, name)
if err != nil {
return nil, err
}
var result []engineapi.Resource
for _, resource := range resources {
result = append(result, engineapi.Resource{
Group: resource.Group,
Version: resource.Version,
Resource: resource.Resource,
SubResource: resource.SubResource,
Unstructured: resource.Unstructured,
})
}
return result, nil
}
func (a *dclientAdapter) GetResource(ctx context.Context, apiVersion, kind, namespace, name string, subresources ...string) (*unstructured.Unstructured, error) {
return a.client.GetResource(ctx, apiVersion, kind, namespace, name, subresources...)
}
func (a *dclientAdapter) CanI(ctx context.Context, kind, namespace, verb, subresource, user string) (bool, error) {
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, verb, subresource, user)
ok, err := canI.RunAccessCheck(ctx)
if err != nil {
return false, err
}
return ok, nil
}

35
pkg/engine/api/client.go Normal file
View file

@ -0,0 +1,35 @@
package api
import (
"context"
"io"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type Resource struct {
Group string
Version string
Resource string
SubResource string
Unstructured unstructured.Unstructured
}
type RawClient interface {
RawAbsPath(ctx context.Context, path string, method string, dataReader io.Reader) ([]byte, error)
}
type AuthClient interface {
CanI(ctx context.Context, kind, namespace, verb, subresource, user string) (bool, error)
}
type ResourceClient interface {
GetResource(ctx context.Context, apiVersion, kind, namespace, name string, subresources ...string) (*unstructured.Unstructured, error)
GetResources(group, version, kind, subresource, namespace, name string) ([]Resource, error)
}
type Client interface {
RawClient
AuthClient
ResourceClient
}

View file

@ -8,7 +8,6 @@ import (
"github.com/go-logr/logr"
"github.com/google/go-containerregistry/pkg/name"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/engine/apicall"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
@ -93,7 +92,7 @@ func LoadImageData(ctx context.Context, jp jmespath.Interface, rclient registryc
return nil
}
func LoadAPIData(ctx context.Context, jp jmespath.Interface, logger logr.Logger, entry kyvernov1.ContextEntry, enginectx enginecontext.Interface, client dclient.Interface) error {
func LoadAPIData(ctx context.Context, jp jmespath.Interface, logger logr.Logger, entry kyvernov1.ContextEntry, enginectx enginecontext.Interface, client RawClient) error {
executor, err := apicall.New(logger, jp, entry, enginectx, client)
if err != nil {
return fmt.Errorf("failed to initialize APICall: %w", err)

View file

@ -6,7 +6,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/logging"
@ -21,7 +20,7 @@ type ContextLoader interface {
Load(
ctx context.Context,
jp jmespath.Interface,
client dclient.Interface,
client Client,
rclient registryclient.Client,
contextEntries []kyvernov1.ContextEntry,
jsonContext enginecontext.Interface,
@ -47,7 +46,7 @@ type contextLoader struct {
func (l *contextLoader) Load(
ctx context.Context,
jp jmespath.Interface,
client dclient.Interface,
client Client,
rclient registryclient.Client,
contextEntries []kyvernov1.ContextEntry,
jsonContext enginecontext.Interface,
@ -66,7 +65,7 @@ func (l *contextLoader) Load(
func (l *contextLoader) newDeferredLoader(
ctx context.Context,
jp jmespath.Interface,
client dclient.Interface,
client Client,
rclient registryclient.Client,
entry kyvernov1.ContextEntry,
jsonContext enginecontext.Interface,

View file

@ -13,7 +13,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/variables"
@ -26,7 +25,11 @@ type apiCall struct {
jp jmespath.Interface
entry kyvernov1.ContextEntry
jsonCtx enginecontext.Interface
client dclient.Interface
client ClientInterface
}
type ClientInterface interface {
RawAbsPath(ctx context.Context, path string, method string, dataReader io.Reader) ([]byte, error)
}
func New(
@ -34,7 +37,7 @@ func New(
jp jmespath.Interface,
entry kyvernov1.ContextEntry,
jsonCtx enginecontext.Interface,
client dclient.Interface,
client ClientInterface,
) (*apiCall, error) {
if entry.APICall == nil {
return nil, fmt.Errorf("missing APICall in context entry %v", entry)

View file

@ -8,7 +8,6 @@ import (
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
@ -31,7 +30,7 @@ type engine struct {
configuration config.Configuration
metricsConfiguration config.MetricsConfiguration
jp jmespath.Interface
client dclient.Interface
client engineapi.Client
rclient registryclient.Client
contextLoader engineapi.ContextLoaderFactory
exceptionSelector engineapi.PolicyExceptionSelector
@ -47,7 +46,7 @@ func NewEngine(
configuration config.Configuration,
metricsConfiguration config.MetricsConfiguration,
jp jmespath.Interface,
client dclient.Interface,
client engineapi.Client,
rclient registryclient.Client,
contextLoader engineapi.ContextLoaderFactory,
exceptionSelector engineapi.PolicyExceptionSelector,

View file

@ -1,12 +1,10 @@
package mutation
import (
"context"
"fmt"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/variables"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
@ -31,7 +29,7 @@ type target struct {
preconditions apiextensions.JSON
}
func loadTargets(client dclient.Interface, targets []kyvernov1.TargetResourceSpec, ctx engineapi.PolicyContext, logger logr.Logger) ([]target, error) {
func loadTargets(client engineapi.Client, targets []kyvernov1.TargetResourceSpec, ctx engineapi.PolicyContext, logger logr.Logger) ([]target, error) {
var targetObjects []target
var errors []error
for i := range targets {
@ -82,7 +80,7 @@ func resolveSpec(i int, target kyvernov1.TargetResourceSpec, ctx engineapi.Polic
}, nil
}
func getTargets(client dclient.Interface, target kyvernov1.ResourceSpec, ctx engineapi.PolicyContext) ([]resourceInfo, error) {
func getTargets(client engineapi.Client, target kyvernov1.ResourceSpec, ctx engineapi.PolicyContext) ([]resourceInfo, error) {
var targetObjects []resourceInfo
namespace := target.Namespace
name := target.Name
@ -92,72 +90,20 @@ func getTargets(client dclient.Interface, target kyvernov1.ResourceSpec, ctx eng
namespace = policy.GetNamespace()
}
group, version, kind, subresource := kubeutils.ParseKindSelector(target.APIVersion + "/" + target.Kind)
gvrss, err := client.Discovery().FindResources(group, version, kind, subresource)
resources, err := client.GetResources(group, version, kind, subresource, namespace, name)
if err != nil {
return nil, err
}
for gvrs := range gvrss {
dyn := client.GetDynamicInterface().Resource(gvrs.GroupVersionResource())
var sub []string
if gvrs.SubResource != "" {
sub = []string{gvrs.SubResource}
}
// we can use `GET` directly
if namespace != "" && name != "" && !wildcard.ContainsWildcard(namespace) && !wildcard.ContainsWildcard(name) {
var obj *unstructured.Unstructured
var err error
obj, err = dyn.Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}, sub...)
if err != nil {
return nil, err
}
targetObjects = append(targetObjects, resourceInfo{
unstructured: *obj,
subresource: gvrs.SubResource,
parentResourceGVR: metav1.GroupVersionResource(gvrs.GroupVersionResource()),
})
} else {
// we can use `LIST`
if gvrs.SubResource == "" {
list, err := dyn.List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, obj := range list.Items {
if match(namespace, name, obj.GetNamespace(), obj.GetName()) {
targetObjects = append(targetObjects, resourceInfo{unstructured: obj})
}
}
} else {
// we need to use `LIST` / `GET`
list, err := dyn.List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
var parentObjects []unstructured.Unstructured
for _, obj := range list.Items {
if match(namespace, name, obj.GetNamespace(), obj.GetName()) {
parentObjects = append(parentObjects, obj)
}
}
for _, parentObject := range parentObjects {
var obj *unstructured.Unstructured
var err error
if parentObject.GetNamespace() == "" {
obj, err = dyn.Get(context.TODO(), name, metav1.GetOptions{}, sub...)
} else {
obj, err = dyn.Namespace(parentObject.GetNamespace()).Get(context.TODO(), name, metav1.GetOptions{}, sub...)
}
if err != nil {
return nil, err
}
targetObjects = append(targetObjects, resourceInfo{
unstructured: *obj,
subresource: gvrs.SubResource,
parentResourceGVR: metav1.GroupVersionResource(gvrs.GroupVersionResource()),
})
}
}
}
for _, resource := range resources {
targetObjects = append(targetObjects, resourceInfo{
unstructured: resource.Unstructured,
subresource: resource.SubResource,
parentResourceGVR: metav1.GroupVersionResource{
Group: resource.Group,
Version: resource.Version,
Resource: resource.Resource,
},
})
}
return targetObjects, nil
}

View file

@ -5,7 +5,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/internal"
@ -15,11 +14,11 @@ import (
)
type mutateExistingHandler struct {
client dclient.Interface
client engineapi.Client
}
func NewMutateExistingHandler(
client dclient.Interface,
client engineapi.Client,
) (handlers.Handler, error) {
return mutateExistingHandler{
client: client,

View file

@ -6,7 +6,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
@ -21,10 +20,10 @@ import (
)
type validateCELHandler struct {
client dclient.Interface
client engineapi.Client
}
func NewValidateCELHandler(client dclient.Interface) (handlers.Handler, error) {
func NewValidateCELHandler(client engineapi.Client) (handlers.Handler, error) {
return validateCELHandler{
client: client,
}, nil

View file

@ -15,8 +15,6 @@ import (
"github.com/ghodss/yaml"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/auth"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
@ -35,12 +33,12 @@ const (
)
type validateManifestHandler struct {
client dclient.Interface
client engineapi.Client
}
func NewValidateManifestHandler(
policyContext engineapi.PolicyContext,
client dclient.Interface,
client engineapi.Client,
) (handlers.Handler, error) {
if engineutils.IsDeleteRequest(policyContext) {
return nil, nil
@ -173,12 +171,7 @@ func (h validateManifestHandler) verifyManifest(
}
func (h validateManifestHandler) checkDryRunPermission(ctx context.Context, kind, namespace string) (bool, error) {
canI := auth.NewCanI(h.client.Discovery(), h.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, "create", "", config.KyvernoServiceAccountName())
ok, err := canI.RunAccessCheck(ctx)
if err != nil {
return false, err
}
return ok, nil
return h.client.CanI(ctx, kind, namespace, "create", "", config.KyvernoServiceAccountName())
}
func verifyManifestAttestorSet(resource unstructured.Unstructured, attestorSet kyvernov1.AttestorSet, vo *k8smanifest.VerifyResourceOption, path string, uid string, logger logr.Logger) (bool, string, error) {

View file

@ -9,6 +9,7 @@ import (
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
client "github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine/adapters"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginetest "github.com/kyverno/kyverno/pkg/engine/test"
"github.com/kyverno/kyverno/pkg/registryclient"
@ -34,7 +35,7 @@ func testMutate(
cfg,
config.NewDefaultMetricsConfiguration(),
jp,
client,
adapters.Client(client),
rclient,
contextLoader,
nil,

View file

@ -5,7 +5,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
@ -46,7 +45,7 @@ type mockContextLoader struct {
func (l *mockContextLoader) Load(
ctx context.Context,
jp jmespath.Interface,
client dclient.Interface,
client engineapi.Client,
rclient registryclient.Client,
contextEntries []kyvernov1.ContextEntry,
jsonContext enginecontext.Interface,

View file

@ -8,6 +8,7 @@ import (
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/adapters"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/context/resolvers"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
@ -58,7 +59,7 @@ func NewFakeHandlers(ctx context.Context, policyCache policycache.Cache) webhook
configuration,
config.NewDefaultMetricsConfiguration(),
jp,
dclient,
adapters.Client(dclient),
rclient,
engineapi.DefaultContextLoaderFactory(configMapResolver),
peLister,