1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-25 00:58:48 +00:00

feat: add parse image reference function ()

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Vishal Choudhary 2025-03-07 13:04:08 +05:30 committed by GitHub
parent 43ddc8c31e
commit 4b4e6cc415
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 89 additions and 9 deletions
pkg
cel
imageverification/imagedataloader

View file

@ -62,6 +62,21 @@ func (c *impl) get_imagedata_string(ctx ref.Val, image ref.Val) ref.Val {
}
}
func (c *impl) parse_imagereference_string(ctx ref.Val, image ref.Val) ref.Val {
if self, err := utils.ConvertToNative[Context](ctx); err != nil {
return types.WrapErr(err)
} else if image, err := utils.ConvertToNative[string](image); err != nil {
return types.WrapErr(err)
} else {
parsedRef, err := self.ParseImageReference(image)
if err != nil {
// Errors are not expected here since Parse is a more lenient parser than ParseRequestURI.
return types.NewErr("failed to parse image data: %v", err)
}
return c.NativeToValue(parsedRef)
}
}
func (c *impl) list_resource_string(args ...ref.Val) ref.Val {
if self, err := utils.ConvertToNative[Context](args[0]); err != nil {
return types.WrapErr(err)

View file

@ -14,11 +14,12 @@ import (
)
type ctx struct {
GetConfigMapFunc func(string, string) (unstructured.Unstructured, error)
GetGlobalReferenceFunc func(string, string) (any, error)
GetImageDataFunc func(string) (*imagedataloader.ImageData, error)
ListResourcesFunc func(string, string, string) (*unstructured.UnstructuredList, error)
GetResourcesFunc func(string, string, string, string) (*unstructured.Unstructured, error)
GetConfigMapFunc func(string, string) (unstructured.Unstructured, error)
GetGlobalReferenceFunc func(string, string) (any, error)
GetImageDataFunc func(string) (*imagedataloader.ImageData, error)
ParseImageReferenceFunc func(string) (imagedataloader.ImageReference, error)
ListResourcesFunc func(string, string, string) (*unstructured.UnstructuredList, error)
GetResourcesFunc func(string, string, string, string) (*unstructured.Unstructured, error)
}
func (mock *ctx) GetConfigMap(ns string, n string) (unstructured.Unstructured, error) {
@ -33,6 +34,10 @@ func (mock *ctx) GetImageData(n string) (*imagedataloader.ImageData, error) {
return mock.GetImageDataFunc(n)
}
func (mock *ctx) ParseImageReference(n string) (imagedataloader.ImageReference, error) {
return mock.ParseImageReferenceFunc(n)
}
func (mock *ctx) ListResource(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error) {
return mock.ListResourcesFunc(apiVersion, resource, namespace)
}
@ -226,6 +231,40 @@ func Test_impl_get_imagedata_string(t *testing.T) {
assert.True(t, img.ImageIndex != nil)
}
func Test_impl_parse_image_ref_string(t *testing.T) {
opts := Lib()
base, err := cel.NewEnv(opts)
assert.NoError(t, err)
assert.NotNil(t, base)
options := []cel.EnvOption{
cel.Variable("context", ContextType),
}
env, err := base.Extend(options...)
assert.NoError(t, err)
assert.NotNil(t, env)
ast, issues := env.Compile(`context.ParseImageReference("ghcr.io/kyverno/kyverno:latest")`)
assert.Nil(t, issues)
assert.NotNil(t, ast)
prog, err := env.Program(ast)
assert.NoError(t, err)
assert.NotNil(t, prog)
data := map[string]any{
"context": Context{&ctx{
ParseImageReferenceFunc: func(image string) (imagedataloader.ImageReference, error) {
idl, err := imagedataloader.New(nil)
assert.NoError(t, err)
return idl.ParseImageReference(image)
},
}},
}
out, _, err := prog.Eval(data)
assert.NoError(t, err)
img := out.Value().(imagedataloader.ImageReference)
assert.Equal(t, img.Tag, "latest")
assert.Equal(t, img.Identifier, "latest")
assert.Equal(t, img.Image, "ghcr.io/kyverno/kyverno:latest")
}
func Test_impl_get_resource_string(t *testing.T) {
opts := Lib()
base, err := cel.NewEnv(opts)

View file

@ -58,6 +58,9 @@ func (c *lib) extendEnv(env *cel.Env) (*cel.Env, error) {
// TODO: should not use DynType in return
cel.MemberOverload("get_imagedata_string", []*cel.Type{ContextType, types.StringType}, imageDataType.CelType(), cel.BinaryBinding(impl.get_imagedata_string)),
},
"ParseImageReference": {
cel.MemberOverload("parse_image_reference_string", []*cel.Type{ContextType, types.StringType}, imageReferenceType.CelType(), cel.BinaryBinding(impl.parse_imagereference_string)),
},
"ListResource": {
// TODO: should not use DynType in return
cel.MemberOverload("list_resource_string", []*cel.Type{ContextType, types.StringType, types.StringType, types.StringType}, types.DynType, cel.FunctionBinding(impl.list_resource_string)),

View file

@ -8,15 +8,17 @@ import (
)
var (
ContextType = types.NewOpaqueType("context.Context")
configMapType = BuildConfigMapType()
imageDataType = BuildImageDataType()
ContextType = types.NewOpaqueType("context.Context")
configMapType = BuildConfigMapType()
imageDataType = BuildImageDataType()
imageReferenceType = BuildImageReferenceType()
)
type ContextInterface interface {
GetConfigMap(string, string) (unstructured.Unstructured, error)
GetGlobalReference(string, string) (any, error)
GetImageData(string) (*imagedataloader.ImageData, error)
ParseImageReference(string) (imagedataloader.ImageReference, error)
ListResource(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error)
GetResource(apiVersion, resource, namespace, name string) (*unstructured.Unstructured, error)
}
@ -71,7 +73,7 @@ func BuildImageDataType() *apiservercel.DeclType {
field("registry", apiservercel.StringType, true),
field("repository", apiservercel.StringType, true),
field("tag", apiservercel.StringType, false),
field("digest", apiservercel.StringType, true),
field("digest", apiservercel.StringType, false),
field("imageIndex", apiservercel.DynType, false),
field("manifest", apiservercel.DynType, true),
field("config", apiservercel.DynType, true),
@ -79,6 +81,20 @@ func BuildImageDataType() *apiservercel.DeclType {
return apiservercel.NewObjectType("imageData", fields(f...))
}
func BuildImageReferenceType() *apiservercel.DeclType {
f := make([]*apiservercel.DeclField, 0)
f = append(f,
field("image", apiservercel.StringType, true),
field("resolvedImage", apiservercel.StringType, true),
field("registry", apiservercel.StringType, true),
field("repository", apiservercel.StringType, true),
field("tag", apiservercel.StringType, false),
field("digest", apiservercel.StringType, false),
field("identifier", apiservercel.StringType, true),
)
return apiservercel.NewObjectType("imageReference", fields(f...))
}
func field(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
return apiservercel.NewDeclField(name, declType, required, nil, nil)
}

View file

@ -149,3 +149,7 @@ func (cp *contextProvider) GetResource(apiVersion, resource, namespace, name str
return resourceInteface.Get(context.TODO(), name, metav1.GetOptions{})
}
func (cp *contextProvider) ParseImageReference(image string) (imagedataloader.ImageReference, error) {
return cp.imagedata.ParseImageReference(image)
}

View file

@ -26,6 +26,7 @@ type imagedatafetcher struct {
type Fetcher interface {
FetchImageData(ctx context.Context, image string, options ...Option) (*ImageData, error)
ParseImageReference(image string, options ...Option) (ImageReference, error)
}
func New(lister k8scorev1.SecretInterface, opts ...Option) (*imagedatafetcher, error) {
@ -51,6 +52,7 @@ func (i *imagedatafetcher) ParseImageReference(image string, options ...Option)
img.Image = image
img.Registry = ref.Context().RegistryStr()
img.Repository = ref.Context().RepositoryStr()
img.Identifier = ref.Identifier()
if _, ok := ref.(name.Tag); ok {
img.Tag = ref.Identifier()
@ -150,6 +152,7 @@ type ImageReference struct {
ResolvedImage string `json:"resolvedImage,omitempty"`
Registry string `json:"registry,omitempty"`
Repository string `json:"repository,omitempty"`
Identifier string `json:"identifier,omitempty"`
Tag string `json:"tag,omitempty"`
Digest string `json:"digest,omitempty"`
}