mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-03-03 18:20:19 +00:00
* fix(controller): decode old object for delete requests Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * fix(config): remove usergroups default Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * fix(config): remove usergroups default Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * sec(ghsa-2ww6-hf35-mfjm): intercept namespace subresource Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> --------- Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
425 lines
11 KiB
Go
425 lines
11 KiB
Go
// Copyright 2020-2026 Project Capsule Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package client_test
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
"github.com/projectcapsule/capsule/pkg/runtime/client"
|
|
)
|
|
|
|
func TestAddLabelsPatch_MapInput(t *testing.T) {
|
|
t.Run("nil labels => add op", func(t *testing.T) {
|
|
var labels map[string]string // nil
|
|
|
|
patches := client.AddLabelsPatch(labels, map[string]string{
|
|
"a": "1",
|
|
})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "add", Path: "/metadata/labels", Value: map[string]string{}},
|
|
{Operation: "add", Path: "/metadata/labels/a", Value: "1"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
|
|
t.Run("existing key same value => no patch", func(t *testing.T) {
|
|
labels := map[string]string{"a": "1"}
|
|
|
|
patches := client.AddLabelsPatch(labels, map[string]string{
|
|
"a": "1",
|
|
})
|
|
|
|
if len(patches) != 0 {
|
|
t.Fatalf("expected no patches, got %v", patches)
|
|
}
|
|
})
|
|
|
|
t.Run("existing key different value => replace op", func(t *testing.T) {
|
|
labels := map[string]string{"a": "1"}
|
|
|
|
patches := client.AddLabelsPatch(labels, map[string]string{
|
|
"a": "2",
|
|
})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "replace", Path: "/metadata/labels/a", Value: "2"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
|
|
t.Run("missing key => add op", func(t *testing.T) {
|
|
labels := map[string]string{"a": "1"}
|
|
|
|
patches := client.AddLabelsPatch(labels, map[string]string{
|
|
"b": "2",
|
|
})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "add", Path: "/metadata/labels/b", Value: "2"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
|
|
t.Run("key contains slash => path escaped with ~1", func(t *testing.T) {
|
|
labels := map[string]string{}
|
|
|
|
patches := client.AddLabelsPatch(labels, map[string]string{
|
|
"projectcapsule.dev/tenant": "wind",
|
|
})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "add", Path: "/metadata/labels/projectcapsule.dev~1tenant", Value: "wind"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestAddAnnotationsPatch_MapInput(t *testing.T) {
|
|
t.Run("nil annotations => add op", func(t *testing.T) {
|
|
var annotations map[string]string // nil
|
|
|
|
patches := client.AddAnnotationsPatch(annotations, map[string]string{
|
|
"a": "1",
|
|
})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "add", Path: "/metadata/annotations", Value: map[string]string{}},
|
|
{Operation: "add", Path: "/metadata/annotations/a", Value: "1"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
|
|
t.Run("existing key same value => no patch", func(t *testing.T) {
|
|
annotations := map[string]string{"a": "1"}
|
|
|
|
patches := client.AddAnnotationsPatch(annotations, map[string]string{
|
|
"a": "1",
|
|
})
|
|
|
|
if len(patches) != 0 {
|
|
t.Fatalf("expected no patches, got %v", patches)
|
|
}
|
|
})
|
|
|
|
t.Run("existing key different value => replace op", func(t *testing.T) {
|
|
annotations := map[string]string{"a": "1"}
|
|
|
|
patches := client.AddAnnotationsPatch(annotations, map[string]string{
|
|
"a": "2",
|
|
})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "replace", Path: "/metadata/annotations/a", Value: "2"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
|
|
t.Run("missing key => add op", func(t *testing.T) {
|
|
annotations := map[string]string{"a": "1"}
|
|
|
|
patches := client.AddAnnotationsPatch(annotations, map[string]string{
|
|
"b": "2",
|
|
})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "add", Path: "/metadata/annotations/b", Value: "2"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
|
|
t.Run("key contains slash => path escaped with ~1", func(t *testing.T) {
|
|
annotations := map[string]string{}
|
|
|
|
patches := client.AddAnnotationsPatch(annotations, map[string]string{
|
|
"example.com/foo": "bar",
|
|
})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "add", Path: "/metadata/annotations/example.com~1foo", Value: "bar"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPatchRemoveLabels_MapInput(t *testing.T) {
|
|
t.Run("nil labels => no patch", func(t *testing.T) {
|
|
var labels map[string]string // nil
|
|
|
|
patches := client.PatchRemoveLabels(labels, []string{"a"})
|
|
if len(patches) != 0 {
|
|
t.Fatalf("expected no patches, got %v", patches)
|
|
}
|
|
})
|
|
|
|
t.Run("existing key => remove patch", func(t *testing.T) {
|
|
labels := map[string]string{"a": "1"}
|
|
|
|
patches := client.PatchRemoveLabels(labels, []string{"a"})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "remove", Path: "/metadata/labels/a"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
|
|
t.Run("missing key => no patch", func(t *testing.T) {
|
|
labels := map[string]string{"a": "1"}
|
|
|
|
patches := client.PatchRemoveLabels(labels, []string{"nope"})
|
|
if len(patches) != 0 {
|
|
t.Fatalf("expected no patches, got %v", patches)
|
|
}
|
|
})
|
|
|
|
t.Run("key contains slash => path escaped with ~1", func(t *testing.T) {
|
|
labels := map[string]string{"projectcapsule.dev/tenant": "wind"}
|
|
|
|
patches := client.PatchRemoveLabels(labels, []string{"projectcapsule.dev/tenant"})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "remove", Path: "/metadata/labels/projectcapsule.dev~1tenant"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPatchRemoveAnnotations_MapInput(t *testing.T) {
|
|
t.Run("nil annotations => no patch", func(t *testing.T) {
|
|
var annotations map[string]string // nil
|
|
|
|
patches := client.PatchRemoveAnnotations(annotations, []string{"a"})
|
|
if len(patches) != 0 {
|
|
t.Fatalf("expected no patches, got %v", patches)
|
|
}
|
|
})
|
|
|
|
t.Run("existing key => remove patch", func(t *testing.T) {
|
|
annotations := map[string]string{"a": "1"}
|
|
|
|
patches := client.PatchRemoveAnnotations(annotations, []string{"a"})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "remove", Path: "/metadata/annotations/a"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
|
|
t.Run("missing key => no patch", func(t *testing.T) {
|
|
annotations := map[string]string{"a": "1"}
|
|
|
|
patches := client.PatchRemoveAnnotations(annotations, []string{"nope"})
|
|
if len(patches) != 0 {
|
|
t.Fatalf("expected no patches, got %v", patches)
|
|
}
|
|
})
|
|
|
|
t.Run("key contains slash => path escaped with ~1", func(t *testing.T) {
|
|
annotations := map[string]string{"example.com/foo": "bar"}
|
|
|
|
patches := client.PatchRemoveAnnotations(annotations, []string{"example.com/foo"})
|
|
|
|
want := []client.JSONPatch{
|
|
{Operation: "remove", Path: "/metadata/annotations/example.com~1foo"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(patches, want) {
|
|
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRemoveOwnerReferencePatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mkRef := func(name, uid string, controller, block bool) metav1.OwnerReference {
|
|
c := controller
|
|
b := block
|
|
return metav1.OwnerReference{
|
|
APIVersion: "v1",
|
|
Kind: "ConfigMap",
|
|
Name: name,
|
|
UID: types.UID(uid),
|
|
Controller: &c,
|
|
BlockOwnerDeletion: &b,
|
|
}
|
|
}
|
|
|
|
t.Run("nil toRemove returns nil", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
refs := []metav1.OwnerReference{mkRef("a", "uid-a", true, true)}
|
|
got := client.RemoveOwnerReferencePatch(refs, nil)
|
|
if got != nil {
|
|
t.Fatalf("expected nil, got %#v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("empty ownerRefs returns nil", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
toRemove := mkRef("a", "uid-a", true, true)
|
|
got := client.RemoveOwnerReferencePatch(nil, &toRemove)
|
|
if got != nil {
|
|
t.Fatalf("expected nil, got %#v", got)
|
|
}
|
|
|
|
got = client.RemoveOwnerReferencePatch([]metav1.OwnerReference{}, &toRemove)
|
|
if got != nil {
|
|
t.Fatalf("expected nil, got %#v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("no matching ownerReference returns nil", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
refs := []metav1.OwnerReference{
|
|
mkRef("a", "uid-a", true, true),
|
|
mkRef("b", "uid-b", false, false),
|
|
}
|
|
// Different UID and name/kind => should not match
|
|
toRemove := mkRef("c", "uid-c", true, true)
|
|
|
|
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
|
|
if got != nil {
|
|
t.Fatalf("expected nil, got %#v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("match in middle returns single remove patch with correct index", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
refs := []metav1.OwnerReference{
|
|
mkRef("a", "uid-a", true, true),
|
|
mkRef("b", "uid-b", false, false),
|
|
mkRef("c", "uid-c", true, false),
|
|
}
|
|
|
|
// Make toRemove identical to refs[1] so LooseOwnerReferenceEqual is true.
|
|
toRemove := refs[1]
|
|
|
|
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
|
|
if got == nil {
|
|
t.Fatalf("expected patches, got nil")
|
|
}
|
|
if len(got) != 1 {
|
|
t.Fatalf("expected 1 patch, got %d: %#v", len(got), got)
|
|
}
|
|
if got[0].Operation != "remove" {
|
|
t.Fatalf("expected op=remove, got %q", got[0].Operation)
|
|
}
|
|
wantPath := "/metadata/ownerReferences/1"
|
|
if got[0].Path != wantPath {
|
|
t.Fatalf("expected path=%q, got %q", wantPath, got[0].Path)
|
|
}
|
|
})
|
|
|
|
t.Run("match first occurrence only", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Duplicate entries (shouldn't happen, but function breaks on first match).
|
|
ref := mkRef("dup", "uid-dup", true, true)
|
|
refs := []metav1.OwnerReference{ref, ref}
|
|
|
|
toRemove := ref
|
|
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
|
|
|
|
if got == nil || len(got) != 1 {
|
|
t.Fatalf("expected 1 patch, got %#v", got)
|
|
}
|
|
wantPath := "/metadata/ownerReferences/0"
|
|
if got[0].Path != wantPath {
|
|
t.Fatalf("expected path=%q, got %q", wantPath, got[0].Path)
|
|
}
|
|
})
|
|
|
|
t.Run("single ownerRef match returns remove element patch AND remove field patch", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
only := mkRef("only", "uid-only", true, true)
|
|
refs := []metav1.OwnerReference{only}
|
|
|
|
toRemove := only
|
|
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
|
|
if got == nil {
|
|
t.Fatalf("expected patches, got nil")
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("expected 2 patches, got %d: %#v", len(got), got)
|
|
}
|
|
|
|
if got[0].Operation != "remove" || got[0].Path != "/metadata/ownerReferences/0" {
|
|
t.Fatalf("unexpected first patch: %#v", got[0])
|
|
}
|
|
if got[1].Operation != "remove" || got[1].Path != "/metadata/ownerReferences" {
|
|
t.Fatalf("unexpected second patch: %#v", got[1])
|
|
}
|
|
})
|
|
|
|
t.Run("index in path is correct for each position", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
refs := []metav1.OwnerReference{
|
|
mkRef("a", "uid-a", true, true),
|
|
mkRef("b", "uid-b", true, true),
|
|
mkRef("c", "uid-c", true, true),
|
|
}
|
|
|
|
for i := range refs {
|
|
i := i
|
|
t.Run(fmt.Sprintf("match index %d", i), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
toRemove := refs[i]
|
|
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
|
|
if got == nil || len(got) != 1 {
|
|
t.Fatalf("expected 1 patch, got %#v", got)
|
|
}
|
|
wantPath := fmt.Sprintf("/metadata/ownerReferences/%d", i)
|
|
if got[0].Path != wantPath {
|
|
t.Fatalf("expected path=%q, got %q", wantPath, got[0].Path)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|