mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-19 12:36:39 +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: preserve ca-bundles injected from external providers Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat: add metadata enforcement Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat: add metadata enforcement Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * fix: add resourcepoolclaim validation Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * fix: add resourcepoolclaim validation Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * fix: add resourcepoolclaim validation Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: add resourcepoolclaim validation Signed-off-by: Oliver Baehler <oliver@sudo-i.net> --------- Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> Signed-off-by: Oliver Baehler <oliver@sudo-i.net> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
328 lines
8.2 KiB
Go
328 lines
8.2 KiB
Go
// Copyright 2020-2026 Project Capsule Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package template
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
"github.com/projectcapsule/capsule/pkg/api/rules"
|
|
"github.com/projectcapsule/capsule/pkg/api/runtime"
|
|
)
|
|
|
|
func TestRenderNamespaceRuleBodies(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
context map[string]any
|
|
key MissingKeyOption
|
|
bodies []*rules.NamespaceRuleBodyNamespace
|
|
assertFn func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace)
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "empty input returns nil",
|
|
key: MissingKeyOption("error"),
|
|
assertFn: func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace) {
|
|
t.Helper()
|
|
|
|
if got != nil {
|
|
t.Fatalf("RenderNamespaceRuleBodies() = %#v, want nil", got)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "renders tenant and namespace values in registry exact match",
|
|
context: map[string]any{
|
|
"tenant": map[string]any{
|
|
"metadata": map[string]any{
|
|
"name": "solar",
|
|
},
|
|
},
|
|
"namespace": map[string]any{
|
|
"metadata": map[string]any{
|
|
"name": "solar-prod",
|
|
},
|
|
},
|
|
},
|
|
key: MissingKeyOption("error"),
|
|
bodies: []*rules.NamespaceRuleBodyNamespace{
|
|
{
|
|
Enforce: &rules.NamespaceRuleEnforceBody{
|
|
Action: rules.ActionTypeAllow,
|
|
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
|
|
Registries: []rules.OCIRegistry{
|
|
{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
Exact: []string{
|
|
"{{ .tenant.metadata.name }}/{{ .namespace.metadata.name }}/app:1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
assertFn: func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace) {
|
|
t.Helper()
|
|
|
|
if len(got) != 1 {
|
|
t.Fatalf("len(got) = %d, want 1", len(got))
|
|
}
|
|
|
|
body := got[0]
|
|
if body == nil || body.Enforce == nil {
|
|
t.Fatalf("got[0].Enforce = nil, want rendered enforce body")
|
|
}
|
|
|
|
if body.Enforce.Action != rules.ActionTypeAllow {
|
|
t.Fatalf("Action = %q, want %q", body.Enforce.Action, rules.ActionTypeAllow)
|
|
}
|
|
|
|
registries := body.Enforce.Workloads.Registries
|
|
if len(registries) != 1 {
|
|
t.Fatalf("len(Registries) = %d, want 1", len(registries))
|
|
}
|
|
|
|
exact := registries[0].Exact
|
|
if len(exact) != 1 || exact[0] != "solar/solar-prod/app:1" {
|
|
t.Fatalf("Exact = %#v, want [solar/solar-prod/app:1]", exact)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "renders namespace labels using index",
|
|
context: map[string]any{
|
|
"namespace": map[string]any{
|
|
"metadata": map[string]any{
|
|
"labels": map[string]any{
|
|
"registry-prefix": "harbor/team-a",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
key: MissingKeyOption("error"),
|
|
bodies: []*rules.NamespaceRuleBodyNamespace{
|
|
{
|
|
Enforce: &rules.NamespaceRuleEnforceBody{
|
|
Action: rules.ActionTypeAllow,
|
|
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
|
|
Registries: []rules.OCIRegistry{
|
|
{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
Exact: []string{
|
|
`{{ index .namespace.metadata.labels "registry-prefix" }}/app:1`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
assertFn: func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace) {
|
|
t.Helper()
|
|
|
|
exact := got[0].Enforce.Workloads.Registries[0].Exact
|
|
if len(exact) != 1 || exact[0] != "harbor/team-a/app:1" {
|
|
t.Fatalf("Exact = %#v, want [harbor/team-a/app:1]", exact)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "renders multiple bodies while preserving order",
|
|
context: map[string]any{
|
|
"tenant": map[string]any{
|
|
"metadata": map[string]any{
|
|
"name": "solar",
|
|
},
|
|
},
|
|
},
|
|
key: MissingKeyOption("error"),
|
|
bodies: []*rules.NamespaceRuleBodyNamespace{
|
|
{
|
|
Enforce: &rules.NamespaceRuleEnforceBody{
|
|
Action: rules.ActionTypeAllow,
|
|
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
|
|
Registries: []rules.OCIRegistry{
|
|
{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
ExpressionRegex: runtime.ExpressionRegex{
|
|
Expression: "{{ .tenant.metadata.name }}/allow/.*",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Enforce: &rules.NamespaceRuleEnforceBody{
|
|
Action: rules.ActionTypeDeny,
|
|
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
|
|
QoSClasses: []corev1.PodQOSClass{
|
|
corev1.PodQOSBestEffort,
|
|
},
|
|
Registries: []rules.OCIRegistry{
|
|
{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
ExpressionRegex: runtime.ExpressionRegex{
|
|
Expression: "{{ .tenant.metadata.name }}/deny/.*",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
assertFn: func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace) {
|
|
t.Helper()
|
|
|
|
if len(got) != 2 {
|
|
t.Fatalf("len(got) = %d, want 2", len(got))
|
|
}
|
|
|
|
if got[0].Enforce.Action != rules.ActionTypeAllow {
|
|
t.Fatalf("got[0].Action = %q, want allow", got[0].Enforce.Action)
|
|
}
|
|
|
|
if got[1].Enforce.Action != rules.ActionTypeDeny {
|
|
t.Fatalf("got[1].Action = %q, want deny", got[1].Enforce.Action)
|
|
}
|
|
|
|
first := got[0].Enforce.Workloads.Registries[0].Expression
|
|
if first != "solar/allow/.*" {
|
|
t.Fatalf("first expression = %q, want solar/allow/.*", first)
|
|
}
|
|
|
|
second := got[1].Enforce.Workloads.Registries[0].Expression
|
|
if second != "solar/deny/.*" {
|
|
t.Fatalf("second expression = %q, want solar/deny/.*", second)
|
|
}
|
|
|
|
qos := got[1].Enforce.Workloads.QoSClasses
|
|
if len(qos) != 1 || qos[0] != corev1.PodQOSBestEffort {
|
|
t.Fatalf("QoSClasses = %#v, want [%q]", qos, corev1.PodQOSBestEffort)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "missing key is wrapped with render namespace rule bodies template",
|
|
context: map[string]any{
|
|
"namespace": map[string]any{
|
|
"metadata": map[string]any{
|
|
"labels": map[string]any{},
|
|
},
|
|
},
|
|
},
|
|
key: MissingKeyOption("error"),
|
|
bodies: []*rules.NamespaceRuleBodyNamespace{
|
|
{
|
|
Enforce: &rules.NamespaceRuleEnforceBody{
|
|
Action: rules.ActionTypeAllow,
|
|
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
|
|
Registries: []rules.OCIRegistry{
|
|
{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
Exact: []string{
|
|
"{{ .namespace.metadata.labels.registry }}/app:1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: "render namespace rule bodies template",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := RenderNamespaceRuleBodies(tt.context, tt.key, tt.bodies)
|
|
if tt.wantErr != "" {
|
|
if err == nil {
|
|
t.Fatalf("RenderNamespaceRuleBodies() error = nil, want containing %q", tt.wantErr)
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), tt.wantErr) {
|
|
t.Fatalf("RenderNamespaceRuleBodies() error = %q, want containing %q", err.Error(), tt.wantErr)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("RenderNamespaceRuleBodies() unexpected error: %v", err)
|
|
}
|
|
|
|
if tt.assertFn != nil {
|
|
tt.assertFn(t, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderNamespaceRuleBodies_DoesNotMutateInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
bodies := []*rules.NamespaceRuleBodyNamespace{
|
|
{
|
|
Enforce: &rules.NamespaceRuleEnforceBody{
|
|
Action: rules.ActionTypeAllow,
|
|
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
|
|
Registries: []rules.OCIRegistry{
|
|
{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
Exact: []string{
|
|
"{{ .tenant.metadata.name }}/app:1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
got, err := RenderNamespaceRuleBodies(
|
|
map[string]any{
|
|
"tenant": map[string]any{
|
|
"metadata": map[string]any{
|
|
"name": "solar",
|
|
},
|
|
},
|
|
},
|
|
MissingKeyOption("error"),
|
|
bodies,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("RenderNamespaceRuleBodies() unexpected error: %v", err)
|
|
}
|
|
|
|
if len(got) != 1 {
|
|
t.Fatalf("len(got) = %d, want 1", len(got))
|
|
}
|
|
|
|
originalExact := bodies[0].Enforce.Workloads.Registries[0].Exact[0]
|
|
if originalExact != "{{ .tenant.metadata.name }}/app:1" {
|
|
t.Fatalf("input body was mutated: exact = %q", originalExact)
|
|
}
|
|
|
|
renderedExact := got[0].Enforce.Workloads.Registries[0].Exact[0]
|
|
if renderedExact != "solar/app:1" {
|
|
t.Fatalf("rendered exact = %q, want solar/app:1", renderedExact)
|
|
}
|
|
}
|