fix: allow replications to own replications without blocking controller updates (#2107)

* chore

* perfromance improvements

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* perfromance improvements

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add globalresourcequota api

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add globalresourcequota api

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add globalresourcequota api

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add globalresourcequota api

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add globalresourcequota api

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add globalresourcequota api

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: remove resource rejections checks

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: allow replications to own replications without blocking controller updates

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: allow replications to own replications without blocking controller updates

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: allow replications to own replications without blocking controller updates

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
This commit is contained in:
Oliver Bähler
2026-08-26 21:59:57 +02:00
committed by GitHub
parent 7ee293a5fd
commit da74cfdab7
25 changed files with 948 additions and 107 deletions
+10 -7
View File
@@ -26,12 +26,15 @@ func ExtraFuncMap() template.FuncMap {
// CustomFuncMap return our custom templates.
func CustomFuncMap() template.FuncMap {
return template.FuncMap{
"toToml": toTOML,
"fromToml": fromTOML,
"fromYamlArray": fromYAMLArray,
"fromJsonArray": fromJSONArray,
"deterministicUUID": deterministicUUID,
"generateAgeKey": generateAgeKey,
"generateAgePQKey": generateAgePQKey,
"toToml": toTOML,
"fromToml": fromTOML,
"fromYamlArray": fromYAMLArray,
"fromJsonArray": fromJSONArray,
"deterministicUUID": deterministicUUID,
"generateAgeKey": generateAgeKey,
"generateAgePQKey": generateAgePQKey,
"getResourceByName": getResourceByName,
"mustGetResourceByName": mustGetResourceByName,
"getResourceByNamespacedName": getResourceByNamespacedName,
}
}
@@ -17,6 +17,9 @@ func TestFuncMaps(t *testing.T) {
"deterministicUUID",
"generateAgeKey",
"generateAgePQKey",
"getResourceByName",
"mustGetResourceByName",
"getResourceByNamespacedName",
} {
if custom[name] == nil {
t.Fatalf("CustomFuncMap()[%q] is nil", name)
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package functions
import "fmt"
func getResourceByName(name string, resources any) (map[string]any, error) {
return findResource(
resources,
fmt.Sprintf("metadata.name %q", name),
func(metadata map[string]any) bool {
return metadata["name"] == name
},
)
}
func mustGetResourceByName(name string, resources any) (map[string]any, error) {
resource, err := getResourceByName(name, resources)
if err != nil {
return nil, err
}
if len(resource) == 0 {
return nil, fmt.Errorf("resource with metadata.name %q was not found", name)
}
return resource, nil
}
func getResourceByNamespacedName(namespace, name string, resources any) (map[string]any, error) {
return findResource(
resources,
fmt.Sprintf("metadata.namespace %q and metadata.name %q", namespace, name),
func(metadata map[string]any) bool {
resourceNamespace, _ := metadata["namespace"].(string)
return resourceNamespace == namespace && metadata["name"] == name
},
)
}
func findResource(
resources any,
description string,
matches func(map[string]any) bool,
) (map[string]any, error) {
items, err := resourceItems(resources)
if err != nil {
return nil, err
}
var match map[string]any
for index, item := range items {
metadata, ok := item["metadata"].(map[string]any)
if !ok {
return nil, fmt.Errorf("resource at index %d has invalid or missing metadata", index)
}
if !matches(metadata) {
continue
}
if match != nil {
return nil, fmt.Errorf("multiple resources match %s", description)
}
match = item
}
if match == nil {
return map[string]any{}, nil
}
return match, nil
}
func resourceItems(resources any) ([]map[string]any, error) {
switch value := resources.(type) {
case nil:
return nil, nil
case []map[string]any:
return value, nil
case []any:
items := make([]map[string]any, 0, len(value))
for index, item := range value {
resource, ok := item.(map[string]any)
if !ok {
return nil, fmt.Errorf("resource at index %d must be an object, got %T", index, item)
}
items = append(items, resource)
}
return items, nil
default:
return nil, fmt.Errorf("resources must be a list of objects, got %T", resources)
}
}
+154
View File
@@ -0,0 +1,154 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package functions
import (
"strings"
"testing"
)
func TestGetResourceByName(t *testing.T) {
t.Parallel()
resources := []map[string]any{
templateResource("team-a", "tenant-management"),
templateResource("team-a", "tenant-settings"),
}
resource, err := getResourceByName("tenant-management", resources)
if err != nil {
t.Fatalf("getResourceByName() error = %v", err)
}
if resource["data"].(map[string]any)["team"] != "team-a" {
t.Fatalf("getResourceByName() = %#v", resource)
}
missing, err := getResourceByName("missing", resources)
if err != nil {
t.Fatalf("getResourceByName(missing) error = %v", err)
}
if len(missing) != 0 {
t.Fatalf("getResourceByName(missing) = %#v, want empty map", missing)
}
}
func TestMustGetResourceByName(t *testing.T) {
t.Parallel()
_, err := mustGetResourceByName("missing", []map[string]any{
templateResource("team-a", "tenant-management"),
})
if err == nil || !strings.Contains(err.Error(), `metadata.name "missing" was not found`) {
t.Fatalf("mustGetResourceByName() error = %v", err)
}
}
func TestGetResourceByNameRejectsAmbiguousMatch(t *testing.T) {
t.Parallel()
resources := []map[string]any{
templateResource("team-a", "tenant-management"),
templateResource("team-b", "tenant-management"),
}
_, err := getResourceByName("tenant-management", resources)
if err == nil || !strings.Contains(err.Error(), "multiple resources match") {
t.Fatalf("getResourceByName() error = %v", err)
}
}
func TestGetResourceByNamespacedName(t *testing.T) {
t.Parallel()
resources := []map[string]any{
templateResource("team-a", "tenant-management"),
templateResource("team-b", "tenant-management"),
}
resource, err := getResourceByNamespacedName("team-b", "tenant-management", resources)
if err != nil {
t.Fatalf("getResourceByNamespacedName() error = %v", err)
}
if resource["data"].(map[string]any)["team"] != "team-b" {
t.Fatalf("getResourceByNamespacedName() = %#v", resource)
}
clusterScoped := templateResource("", "shared")
delete(clusterScoped["metadata"].(map[string]any), "namespace")
resource, err = getResourceByNamespacedName("", "shared", []map[string]any{clusterScoped})
if err != nil {
t.Fatalf("getResourceByNamespacedName(cluster-scoped) error = %v", err)
}
if resource["metadata"].(map[string]any)["name"] != "shared" {
t.Fatalf("getResourceByNamespacedName(cluster-scoped) = %#v", resource)
}
}
func TestGetResourceSupportsJSONRoundTrippedContext(t *testing.T) {
t.Parallel()
resources := []any{
templateResource("team-a", "tenant-management"),
}
resource, err := getResourceByName("tenant-management", resources)
if err != nil {
t.Fatalf("getResourceByName() error = %v", err)
}
if resource["data"].(map[string]any)["team"] != "team-a" {
t.Fatalf("getResourceByName() = %#v", resource)
}
}
func TestGetResourceRejectsInvalidContext(t *testing.T) {
t.Parallel()
tests := []struct {
name string
resources any
wantErr string
}{
{
name: "not a list",
resources: map[string]any{},
wantErr: "resources must be a list of objects",
},
{
name: "list item is not an object",
resources: []any{"invalid"},
wantErr: "resource at index 0 must be an object",
},
{
name: "missing metadata",
resources: []map[string]any{{}},
wantErr: "resource at index 0 has invalid or missing metadata",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
_, err := getResourceByName("tenant-management", test.resources)
if err == nil || !strings.Contains(err.Error(), test.wantErr) {
t.Fatalf("getResourceByName() error = %v, want containing %q", err, test.wantErr)
}
})
}
}
func templateResource(namespace, name string) map[string]any {
return map[string]any{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]any{
"namespace": namespace,
"name": name,
},
"data": map[string]any{
"team": namespace,
},
}
}
+18
View File
@@ -63,6 +63,24 @@ func TestRenderTemplateBytes(t *testing.T) {
tpl: `{{ .registry | upper }}/app:1`,
want: "HARBOR/app:1",
},
{
name: "finds a context resource by name",
context: map[string]any{
"mgmt": []map[string]any{
{
"metadata": map[string]any{"name": "tenant-management"},
"data": map[string]any{
"team-a": "subjects:\n - name: alice\n - name: bob\n",
},
},
},
},
key: MissingKeyOption("error"),
tpl: `{{- $resource := .mgmt | mustGetResourceByName "tenant-management" -}}
{{- $team := $resource.data | get "team-a" | fromYAML -}}
{{- range $team.subjects }}{{ .name }} {{ end -}}`,
want: "alice bob ",
},
{
name: "missing key returns execute error when missingkey error is enabled",
context: map[string]any{