mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-09-08 16:27:17 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user