1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

refactor: combine unstructured and resource packages (#8276)

* refactor: introduce userinfo package in the cli

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* refactor: introduce api package in cli

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* refactor: combine unstructured and resource packages

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-09-05 23:09:31 +02:00 committed by GitHub
parent 34bfb57c08
commit 5360248135
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 310 additions and 280 deletions

View file

@ -5,7 +5,6 @@ import (
"github.com/go-git/go-billy/v5"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource"
unstructuredutils "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/unstructured"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -14,9 +13,9 @@ func getAndCompareResource(actualResource unstructured.Unstructured, fs billy.Fi
if err != nil {
return false, fmt.Errorf("Error: failed to load resource (%s)", err)
}
unstructuredutils.FixupGenerateLabels(actualResource)
unstructuredutils.FixupGenerateLabels(*expectedResource)
equals, err := unstructuredutils.Compare(actualResource, *expectedResource, true)
resource.FixupGenerateLabels(actualResource)
resource.FixupGenerateLabels(*expectedResource)
equals, err := resource.Compare(actualResource, *expectedResource, true)
if err != nil {
return false, fmt.Errorf("Error: failed to compare resources (%s)", err)
}

View file

@ -0,0 +1,46 @@
package resource
import (
jsonpatch "github.com/evanphx/json-patch/v5"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type (
marshaler = func(*unstructured.Unstructured) ([]byte, error)
patcher = func(originalJSON, modifiedJSON []byte) ([]byte, error)
)
var (
defaultMarshaler = (*unstructured.Unstructured).MarshalJSON
defaultPatcher = jsonpatch.CreateMergePatch
)
func Compare(a, e unstructured.Unstructured, tidy bool) (bool, error) {
if tidy {
a = Tidy(a)
e = Tidy(e)
}
return compare(a, e, defaultMarshaler, defaultPatcher)
}
func compare(a, e unstructured.Unstructured, marshaler marshaler, patcher patcher) (bool, error) {
if marshaler == nil {
marshaler = defaultMarshaler
}
actual, err := marshaler(&a)
if err != nil {
return false, err
}
expected, err := marshaler(&e)
if err != nil {
return false, err
}
if patcher == nil {
patcher = defaultPatcher
}
patch, err := patcher(actual, expected)
if err != nil {
return false, err
}
return len(patch) == 2, nil
}

View file

@ -1,100 +1,12 @@
package unstructured
package resource
import (
"errors"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestTidyObject(t *testing.T) {
tests := []struct {
name string
obj interface{}
want interface{}
}{{
obj: "string",
want: "string",
}, {
obj: map[string]interface{}{},
want: nil,
}, {
obj: nil,
want: nil,
}, {
obj: []interface{}{},
want: nil,
}, {
obj: map[string]interface{}{
"map": nil,
},
want: nil,
}, {
obj: map[string]interface{}{
"map": map[string]interface{}{},
},
want: nil,
}, {
obj: map[string]interface{}{
"map": map[string]interface{}{
"foo": "bar",
},
},
want: map[string]interface{}{
"map": map[string]interface{}{
"foo": "bar",
},
},
}, {
obj: []interface{}{[]interface{}{}},
want: nil,
}, {
obj: []interface{}{1},
want: []interface{}{1},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := TidyObject(tt.obj); !reflect.DeepEqual(got, tt.want) {
t.Errorf("TidyObject() = %v, want %v", got, tt.want)
}
})
}
}
func TestTidy(t *testing.T) {
tests := []struct {
name string
obj unstructured.Unstructured
want unstructured.Unstructured
}{{
obj: unstructured.Unstructured{},
want: unstructured.Unstructured{},
}, {
obj: unstructured.Unstructured{
Object: map[string]interface{}{
"map": map[string]interface{}{
"foo": "bar",
},
},
},
want: unstructured.Unstructured{
Object: map[string]interface{}{
"map": map[string]interface{}{
"foo": "bar",
},
},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Tidy(tt.obj); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Tidy() = %v, want %v", got, tt.want)
}
})
}
}
func TestCompare(t *testing.T) {
tests := []struct {
name string
@ -186,83 +98,6 @@ func TestCompare(t *testing.T) {
}
}
func TestFixupGenerateLabels(t *testing.T) {
tests := []struct {
name string
obj unstructured.Unstructured
want unstructured.Unstructured
}{{
name: "not set",
}, {
name: "empty",
obj: unstructured.Unstructured{Object: map[string]interface{}{}},
want: unstructured.Unstructured{Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app.kubernetes.io/managed-by": "kyverno",
},
},
}},
}, {
name: "with label",
obj: unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app.kubernetes.io/managed-by": "kyverno",
},
},
},
},
want: unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app.kubernetes.io/managed-by": "kyverno",
},
},
},
},
}, {
name: "with generate labels",
obj: unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"foo": "bar",
"generate.kyverno.io/policy-name": "add-networkpolicy",
"generate.kyverno.io/policy-namespace": "",
"generate.kyverno.io/rule-name": "default-deny",
"generate.kyverno.io/trigger-group": "",
"generate.kyverno.io/trigger-kind": "Namespace",
"generate.kyverno.io/trigger-name": "hello-world-namespace",
"generate.kyverno.io/trigger-namespace": "default",
"generate.kyverno.io/trigger-version": "v1",
},
},
},
},
want: unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app.kubernetes.io/managed-by": "kyverno",
"foo": "bar",
},
},
},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
FixupGenerateLabels(tt.obj)
if !reflect.DeepEqual(tt.obj, tt.want) {
t.Errorf("FixupGenerateLabels() = %v, want %v", tt.obj, tt.want)
}
})
}
}
func Test_compare(t *testing.T) {
errorMarshaller := func(count int) marshaler {
var current = 0

View file

@ -0,0 +1,21 @@
package resource
import (
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func FixupGenerateLabels(obj unstructured.Unstructured) {
tidy := map[string]string{
"app.kubernetes.io/managed-by": "kyverno",
}
if labels := obj.GetLabels(); labels != nil {
for k, v := range labels {
if !strings.HasPrefix(k, "generate.kyverno.io/") {
tidy[k] = v
}
}
}
obj.SetLabels(tidy)
}

View file

@ -0,0 +1,85 @@
package resource
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestFixupGenerateLabels(t *testing.T) {
tests := []struct {
name string
obj unstructured.Unstructured
want unstructured.Unstructured
}{{
name: "not set",
}, {
name: "empty",
obj: unstructured.Unstructured{Object: map[string]interface{}{}},
want: unstructured.Unstructured{Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app.kubernetes.io/managed-by": "kyverno",
},
},
}},
}, {
name: "with label",
obj: unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app.kubernetes.io/managed-by": "kyverno",
},
},
},
},
want: unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app.kubernetes.io/managed-by": "kyverno",
},
},
},
},
}, {
name: "with generate labels",
obj: unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"foo": "bar",
"generate.kyverno.io/policy-name": "add-networkpolicy",
"generate.kyverno.io/policy-namespace": "",
"generate.kyverno.io/rule-name": "default-deny",
"generate.kyverno.io/trigger-group": "",
"generate.kyverno.io/trigger-kind": "Namespace",
"generate.kyverno.io/trigger-name": "hello-world-namespace",
"generate.kyverno.io/trigger-namespace": "default",
"generate.kyverno.io/trigger-version": "v1",
},
},
},
},
want: unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app.kubernetes.io/managed-by": "kyverno",
"foo": "bar",
},
},
},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
FixupGenerateLabels(tt.obj)
if !reflect.DeepEqual(tt.obj, tt.want) {
t.Errorf("FixupGenerateLabels() = %v, want %v", tt.obj, tt.want)
}
})
}
}

View file

@ -0,0 +1,45 @@
package resource
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func Tidy(obj unstructured.Unstructured) unstructured.Unstructured {
if obj.Object == nil {
return obj
}
return unstructured.Unstructured{
Object: tidy(obj.UnstructuredContent()).(map[string]interface{}),
}
}
func tidy(obj interface{}) interface{} {
switch typedPatternElement := obj.(type) {
case map[string]interface{}:
out := map[string]interface{}{}
for k, v := range typedPatternElement {
v = tidy(v)
if v != nil {
out[k] = v
}
}
if len(out) == 0 {
return nil
}
return out
case []interface{}:
var out []interface{}
for _, v := range typedPatternElement {
v = tidy(v)
if v != nil {
out = append(out, v)
}
}
if len(out) == 0 {
return nil
}
return out
default:
return obj
}
}

View file

@ -0,0 +1,95 @@
package resource
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func Test_tidy(t *testing.T) {
tests := []struct {
name string
obj interface{}
want interface{}
}{{
obj: "string",
want: "string",
}, {
obj: map[string]interface{}{},
want: nil,
}, {
obj: nil,
want: nil,
}, {
obj: []interface{}{},
want: nil,
}, {
obj: map[string]interface{}{
"map": nil,
},
want: nil,
}, {
obj: map[string]interface{}{
"map": map[string]interface{}{},
},
want: nil,
}, {
obj: map[string]interface{}{
"map": map[string]interface{}{
"foo": "bar",
},
},
want: map[string]interface{}{
"map": map[string]interface{}{
"foo": "bar",
},
},
}, {
obj: []interface{}{[]interface{}{}},
want: nil,
}, {
obj: []interface{}{1},
want: []interface{}{1},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tidy(tt.obj); !reflect.DeepEqual(got, tt.want) {
t.Errorf("TidyObject() = %v, want %v", got, tt.want)
}
})
}
}
func TestTidy(t *testing.T) {
tests := []struct {
name string
obj unstructured.Unstructured
want unstructured.Unstructured
}{{
obj: unstructured.Unstructured{},
want: unstructured.Unstructured{},
}, {
obj: unstructured.Unstructured{
Object: map[string]interface{}{
"map": map[string]interface{}{
"foo": "bar",
},
},
},
want: unstructured.Unstructured{
Object: map[string]interface{}{
"map": map[string]interface{}{
"foo": "bar",
},
},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Tidy(tt.obj); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Tidy() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -12,8 +12,7 @@ import (
"sigs.k8s.io/yaml"
)
func Load(fs billy.Filesystem, path string, resourcePath string) (*kyvernov1beta1.RequestInfo, error) {
var userInfo kyvernov1beta1.RequestInfo
func load(fs billy.Filesystem, path string, resourcePath string) ([]byte, error) {
if fs != nil {
filep, err := fs.Open(filepath.Join(resourcePath, path))
if err != nil {
@ -23,17 +22,24 @@ func Load(fs billy.Filesystem, path string, resourcePath string) (*kyvernov1beta
if err != nil {
return nil, fmt.Errorf("Error: failed to read file %s: %w", filep.Name(), err)
}
if err := yaml.UnmarshalStrict(bytes, &userInfo); err != nil {
return nil, sanitizederror.NewWithError("failed to decode yaml", err)
}
return bytes, err
} else {
bytes, err := os.ReadFile(filepath.Clean(filepath.Join(resourcePath, path)))
if err != nil {
return nil, sanitizederror.NewWithError("unable to read yaml", err)
}
if err := yaml.UnmarshalStrict(bytes, &userInfo); err != nil {
return nil, sanitizederror.NewWithError("failed to decode yaml", err)
}
return bytes, err
}
}
func Load(fs billy.Filesystem, path string, resourcePath string) (*kyvernov1beta1.RequestInfo, error) {
bytes, err := load(fs, path, resourcePath)
if err != nil {
return nil, sanitizederror.NewWithError("unable to read yaml", err)
}
var userInfo kyvernov1beta1.RequestInfo
if err := yaml.UnmarshalStrict(bytes, &userInfo); err != nil {
return nil, sanitizederror.NewWithError("failed to decode yaml", err)
}
return &userInfo, nil
}

View file

@ -1,102 +0,0 @@
package unstructured
import (
"strings"
jsonpatch "github.com/evanphx/json-patch/v5"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type (
marshaler = func(*unstructured.Unstructured) ([]byte, error)
patcher = func(originalJSON, modifiedJSON []byte) ([]byte, error)
)
var (
defaultMarshaler = (*unstructured.Unstructured).MarshalJSON
defaultPatcher = jsonpatch.CreateMergePatch
)
func TidyObject(obj interface{}) interface{} {
switch typedPatternElement := obj.(type) {
case map[string]interface{}:
tidy := map[string]interface{}{}
for k, v := range typedPatternElement {
v = TidyObject(v)
if v != nil {
tidy[k] = v
}
}
if len(tidy) == 0 {
return nil
}
return tidy
case []interface{}:
var tidy []interface{}
for _, v := range typedPatternElement {
v = TidyObject(v)
if v != nil {
tidy = append(tidy, v)
}
}
if len(tidy) == 0 {
return nil
}
return tidy
default:
return obj
}
}
func Tidy(obj unstructured.Unstructured) unstructured.Unstructured {
if obj.Object == nil {
return obj
}
return unstructured.Unstructured{
Object: TidyObject(obj.UnstructuredContent()).(map[string]interface{}),
}
}
func FixupGenerateLabels(obj unstructured.Unstructured) {
tidy := map[string]string{
"app.kubernetes.io/managed-by": "kyverno",
}
if labels := obj.GetLabels(); labels != nil {
for k, v := range labels {
if !strings.HasPrefix(k, "generate.kyverno.io/") {
tidy[k] = v
}
}
}
obj.SetLabels(tidy)
}
func Compare(a, e unstructured.Unstructured, tidy bool) (bool, error) {
if tidy {
a = Tidy(a)
e = Tidy(e)
}
return compare(a, e, defaultMarshaler, defaultPatcher)
}
func compare(a, e unstructured.Unstructured, marshaler marshaler, patcher patcher) (bool, error) {
if marshaler == nil {
marshaler = defaultMarshaler
}
actual, err := marshaler(&a)
if err != nil {
return false, err
}
expected, err := marshaler(&e)
if err != nil {
return false, err
}
if patcher == nil {
patcher = defaultPatcher
}
patch, err := patcher(actual, expected)
if err != nil {
return false, err
}
return len(patch) == 2, nil
}