mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-19 04:26:45 +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>
967 lines
21 KiB
Go
967 lines
21 KiB
Go
// Copyright 2020-2026 Project Capsule Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package cache
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
"github.com/projectcapsule/capsule/pkg/api/rules"
|
|
"github.com/projectcapsule/capsule/pkg/api/runtime"
|
|
)
|
|
|
|
func TestNewRegistryRuleSetCache(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("creates cache with default regex cache", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
if c == nil {
|
|
t.Fatal("expected cache, got nil")
|
|
}
|
|
|
|
if c.regexCache == nil {
|
|
t.Fatal("expected regex cache to be initialized")
|
|
}
|
|
|
|
if c.rs == nil {
|
|
t.Fatal("expected rule set map to be initialized")
|
|
}
|
|
|
|
if got := c.Stats(); got != 0 {
|
|
t.Fatalf("expected empty cache, got stats=%d", got)
|
|
}
|
|
})
|
|
|
|
t.Run("uses provided regex cache", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
regexCache := NewRegexCache()
|
|
c := NewRegistryRuleSetCache(regexCache)
|
|
|
|
if c.regexCache != regexCache {
|
|
t.Fatal("expected provided regex cache to be used")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheGetOrBuild(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
rules []rules.OCIRegistry
|
|
wantNil bool
|
|
wantCache int
|
|
}{
|
|
{
|
|
name: "empty rules return nil ruleset",
|
|
rules: nil,
|
|
wantNil: true,
|
|
wantCache: 0,
|
|
},
|
|
{
|
|
name: "single registry builds ruleset",
|
|
rules: []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
},
|
|
wantNil: false,
|
|
wantCache: 1,
|
|
},
|
|
{
|
|
name: "multiple registries build one ruleset",
|
|
rules: []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
registry("ghcr.io/.*"),
|
|
},
|
|
wantNil: false,
|
|
wantCache: 1,
|
|
},
|
|
{
|
|
name: "registry with policy builds ruleset",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullNever),
|
|
},
|
|
wantNil: false,
|
|
wantCache: 1,
|
|
},
|
|
{
|
|
name: "registry with negated expression builds ruleset",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithExpression(runtime.ExpressionRegex{
|
|
Expression: "trusted/.*",
|
|
Negate: true,
|
|
}),
|
|
},
|
|
wantNil: false,
|
|
wantCache: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
rs, fromCache, err := c.GetOrBuild(tt.rules)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if fromCache {
|
|
t.Fatal("expected first build to not come from cache")
|
|
}
|
|
|
|
if tt.wantNil {
|
|
if rs != nil {
|
|
t.Fatalf("expected nil ruleset, got %#v", rs)
|
|
}
|
|
} else if rs == nil {
|
|
t.Fatal("expected ruleset, got nil")
|
|
}
|
|
|
|
if got := c.Stats(); got != tt.wantCache {
|
|
t.Fatalf("expected cache stats=%d, got %d", tt.wantCache, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheGetOrBuildReturnsFromCache(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
specRules := []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
registryWithPolicy("ghcr.io/.*", corev1.PullIfNotPresent),
|
|
}
|
|
|
|
first, fromCache, err := c.GetOrBuild(specRules)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if fromCache {
|
|
t.Fatal("expected first call to build ruleset")
|
|
}
|
|
|
|
second, fromCache, err := c.GetOrBuild(specRules)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if !fromCache {
|
|
t.Fatal("expected second call to come from cache")
|
|
}
|
|
|
|
if first != second {
|
|
t.Fatal("expected same ruleset pointer from cache")
|
|
}
|
|
|
|
if got := c.Stats(); got != 1 {
|
|
t.Fatalf("expected one cached ruleset, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheGetOrBuildConcurrent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
specRules := []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
registryWithPolicy("ghcr.io/.*", corev1.PullIfNotPresent),
|
|
}
|
|
|
|
const workers = 32
|
|
|
|
var wg sync.WaitGroup
|
|
errs := make(chan error, workers)
|
|
results := make(chan *RuleSet, workers)
|
|
|
|
for i := 0; i < workers; i++ {
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
rs, _, err := c.GetOrBuild(specRules)
|
|
if err != nil {
|
|
errs <- err
|
|
|
|
return
|
|
}
|
|
|
|
results <- rs
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
close(errs)
|
|
close(results)
|
|
|
|
for err := range errs {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var first *RuleSet
|
|
for rs := range results {
|
|
if rs == nil {
|
|
t.Fatal("expected ruleset, got nil")
|
|
}
|
|
|
|
if first == nil {
|
|
first = rs
|
|
|
|
continue
|
|
}
|
|
|
|
if rs != first {
|
|
t.Fatal("expected all goroutines to receive the same cached ruleset")
|
|
}
|
|
}
|
|
|
|
if got := c.Stats(); got != 1 {
|
|
t.Fatalf("expected one cached ruleset, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheBuildRuleSet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
specRules := []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
registryWithPolicy("ghcr.io/.*", corev1.PullAlways, corev1.PullIfNotPresent),
|
|
registryWithExpression(runtime.ExpressionRegex{
|
|
Expression: "trusted/.*",
|
|
Negate: true,
|
|
}),
|
|
}
|
|
|
|
id := c.HashRules(specRules)
|
|
|
|
rs, err := c.buildRuleSet(id, specRules)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if rs == nil {
|
|
t.Fatal("expected ruleset, got nil")
|
|
}
|
|
|
|
if rs.ID != id {
|
|
t.Fatalf("expected ruleset ID %q, got %q", id, rs.ID)
|
|
}
|
|
|
|
if len(rs.Compiled) != len(specRules) {
|
|
t.Fatalf("expected %d compiled rules, got %d", len(specRules), len(rs.Compiled))
|
|
}
|
|
|
|
if rs.Compiled[0].Match.Expression != "harbor/.*" {
|
|
t.Fatalf("expected first expression harbor/.*, got %q", rs.Compiled[0].Match.Expression)
|
|
}
|
|
|
|
if len(rs.Compiled[0].AllowedPolicy) != 0 {
|
|
t.Fatal("expected first rule to allow any pull policy")
|
|
}
|
|
|
|
if rs.Compiled[1].Match.Expression != "ghcr.io/.*" {
|
|
t.Fatalf("expected second expression ghcr.io/.*, got %q", rs.Compiled[1].Match.Expression)
|
|
}
|
|
|
|
if len(rs.Compiled[1].AllowedPolicy) != 2 {
|
|
t.Fatalf("expected two allowed pull policies, got %d", len(rs.Compiled[1].AllowedPolicy))
|
|
}
|
|
|
|
if _, ok := rs.Compiled[1].AllowedPolicy[corev1.PullAlways]; !ok {
|
|
t.Fatal("expected PullAlways to be allowed")
|
|
}
|
|
|
|
if _, ok := rs.Compiled[1].AllowedPolicy[corev1.PullIfNotPresent]; !ok {
|
|
t.Fatal("expected PullIfNotPresent to be allowed")
|
|
}
|
|
|
|
if !rs.Compiled[2].Match.Negate {
|
|
t.Fatal("expected third expression to be negated")
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheBuildRuleSetInvalidRegex(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
_, _, err := c.GetOrBuild([]rules.OCIRegistry{
|
|
registry("["),
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected invalid regex error, got nil")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "error parsing regexp") {
|
|
t.Fatalf("expected regexp parse error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheMatchReference(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
rules []rules.OCIRegistry
|
|
reference string
|
|
wantMatch bool
|
|
wantExpr string
|
|
}{
|
|
{
|
|
name: "matches first regex",
|
|
rules: []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
},
|
|
reference: "harbor/team/app:1",
|
|
wantMatch: true,
|
|
wantExpr: "harbor/.*",
|
|
},
|
|
{
|
|
name: "does not match unmatched regex",
|
|
rules: []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
},
|
|
reference: "ghcr.io/team/app:1",
|
|
wantMatch: false,
|
|
},
|
|
{
|
|
name: "returns first matching compiled rule",
|
|
rules: []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
registry("harbor/customer/.*"),
|
|
},
|
|
reference: "harbor/customer/app:1",
|
|
wantMatch: true,
|
|
wantExpr: "harbor/.*",
|
|
},
|
|
{
|
|
name: "matches later regex when first does not match",
|
|
rules: []rules.OCIRegistry{
|
|
registry("ghcr.io/.*"),
|
|
registry("harbor/customer/.*"),
|
|
},
|
|
reference: "harbor/customer/app:1",
|
|
wantMatch: true,
|
|
wantExpr: "harbor/customer/.*",
|
|
},
|
|
{
|
|
name: "negated expression matches non-matching reference",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithExpression(runtime.ExpressionRegex{
|
|
Expression: "trusted/.*",
|
|
Negate: true,
|
|
}),
|
|
},
|
|
// regexp.MatchString is not anchored by default. Do not use
|
|
// "untrusted/..." here, because it contains "trusted/..." as a substring.
|
|
reference: "docker.io/team/app:1",
|
|
wantMatch: true,
|
|
wantExpr: "trusted/.*",
|
|
},
|
|
{
|
|
name: "negated expression does not match matching reference",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithExpression(runtime.ExpressionRegex{
|
|
Expression: "trusted/.*",
|
|
Negate: true,
|
|
}),
|
|
},
|
|
reference: "trusted/team/app:1",
|
|
wantMatch: false,
|
|
},
|
|
{
|
|
name: "policy does not affect MatchReference",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullNever),
|
|
},
|
|
reference: "harbor/team/app:1",
|
|
wantMatch: true,
|
|
wantExpr: "harbor/.*",
|
|
},
|
|
{
|
|
name: "legacy url fallback is used as expression",
|
|
rules: []rules.OCIRegistry{
|
|
registry("legacy/.*"),
|
|
},
|
|
reference: "legacy/team/app:1",
|
|
wantMatch: true,
|
|
wantExpr: "legacy/.*",
|
|
},
|
|
{
|
|
name: "nested regex expression wins over legacy url",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithExpression(runtime.ExpressionRegex{
|
|
Expression: "nested/.*",
|
|
}),
|
|
},
|
|
reference: "nested/team/app:1",
|
|
wantMatch: true,
|
|
wantExpr: "nested/.*",
|
|
},
|
|
{
|
|
name: "legacy url is ignored when nested regex expression is set",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithExpression(runtime.ExpressionRegex{
|
|
Expression: "nested/.*",
|
|
}),
|
|
},
|
|
reference: "legacy/team/app:1",
|
|
wantMatch: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
rs, _, err := c.GetOrBuild(tt.rules)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
got, err := c.MatchReference(rs, tt.reference)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if tt.wantMatch {
|
|
if got == nil {
|
|
t.Fatal("expected match, got nil")
|
|
}
|
|
|
|
if got.Match.Expression != tt.wantExpr {
|
|
t.Fatalf("expected expression %q, got %q", tt.wantExpr, got.Match.Expression)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if got != nil {
|
|
t.Fatalf("expected no match, got %#v", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheMatchRuleSetWithPullPolicy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
rules []rules.OCIRegistry
|
|
reference string
|
|
pullPolicy corev1.PullPolicy
|
|
wantMatch bool
|
|
wantExpr string
|
|
}{
|
|
{
|
|
name: "matches when no policy is configured",
|
|
rules: []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
},
|
|
reference: "harbor/team/app:1",
|
|
pullPolicy: corev1.PullAlways,
|
|
wantMatch: true,
|
|
wantExpr: "harbor/.*",
|
|
},
|
|
{
|
|
name: "matches allowed pull policy",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullNever),
|
|
},
|
|
reference: "harbor/team/app:1",
|
|
pullPolicy: corev1.PullNever,
|
|
wantMatch: true,
|
|
wantExpr: "harbor/.*",
|
|
},
|
|
{
|
|
name: "does not match forbidden pull policy",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullNever),
|
|
},
|
|
reference: "harbor/team/app:1",
|
|
pullPolicy: corev1.PullIfNotPresent,
|
|
wantMatch: false,
|
|
},
|
|
{
|
|
name: "does not match empty pull policy when policy is configured",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullNever),
|
|
},
|
|
reference: "harbor/team/app:1",
|
|
pullPolicy: "",
|
|
wantMatch: false,
|
|
},
|
|
{
|
|
name: "later rule can match when earlier policy rejects",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullNever),
|
|
registryWithPolicy("harbor/customer/.*", corev1.PullIfNotPresent),
|
|
},
|
|
reference: "harbor/customer/app:1",
|
|
pullPolicy: corev1.PullIfNotPresent,
|
|
wantMatch: true,
|
|
wantExpr: "harbor/customer/.*",
|
|
},
|
|
{
|
|
name: "negated expression respects pull policy",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithExpressionAndPolicy(runtime.ExpressionRegex{
|
|
Expression: "trusted/.*",
|
|
Negate: true,
|
|
}, corev1.PullIfNotPresent),
|
|
},
|
|
// regexp.MatchString is not anchored by default. Do not use
|
|
// "untrusted/..." here, because it contains "trusted/..." as a substring.
|
|
reference: "docker.io/team/app:1",
|
|
pullPolicy: corev1.PullIfNotPresent,
|
|
wantMatch: true,
|
|
wantExpr: "trusted/.*",
|
|
},
|
|
{
|
|
name: "negated expression still rejects forbidden pull policy",
|
|
rules: []rules.OCIRegistry{
|
|
registryWithExpressionAndPolicy(runtime.ExpressionRegex{
|
|
Expression: "trusted/.*",
|
|
Negate: true,
|
|
}, corev1.PullNever),
|
|
},
|
|
// regexp.MatchString is not anchored by default. Do not use
|
|
// "untrusted/..." here, because it contains "trusted/..." as a substring.
|
|
reference: "docker.io/team/app:1",
|
|
pullPolicy: corev1.PullIfNotPresent,
|
|
wantMatch: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
rs, _, err := c.GetOrBuild(tt.rules)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
got, err := c.MatchRuleSet(rs, tt.reference, tt.pullPolicy)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if tt.wantMatch {
|
|
if got == nil {
|
|
t.Fatal("expected match, got nil")
|
|
}
|
|
|
|
if got.Match.Expression != tt.wantExpr {
|
|
t.Fatalf("expected expression %q, got %q", tt.wantExpr, got.Match.Expression)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if got != nil {
|
|
t.Fatalf("expected no match, got %#v", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheMatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
got, err := c.Match(
|
|
[]rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullIfNotPresent),
|
|
},
|
|
"harbor/team/app:1",
|
|
corev1.PullIfNotPresent,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if got == nil {
|
|
t.Fatal("expected match, got nil")
|
|
}
|
|
|
|
if got.Match.Expression != "harbor/.*" {
|
|
t.Fatalf("expected harbor/.*, got %q", got.Match.Expression)
|
|
}
|
|
}
|
|
|
|
func TestCompiledRuleAllowsPullPolicy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("nil policy allows any pull policy", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rule := &CompiledRule{}
|
|
|
|
if !rule.AllowsPullPolicy(corev1.PullAlways) {
|
|
t.Fatal("expected PullAlways to be allowed")
|
|
}
|
|
|
|
if !rule.AllowsPullPolicy(corev1.PullIfNotPresent) {
|
|
t.Fatal("expected PullIfNotPresent to be allowed")
|
|
}
|
|
|
|
if !rule.AllowsPullPolicy(corev1.PullNever) {
|
|
t.Fatal("expected PullNever to be allowed")
|
|
}
|
|
|
|
if !rule.AllowsPullPolicy("") {
|
|
t.Fatal("expected empty pull policy to be allowed when no policy is configured")
|
|
}
|
|
})
|
|
|
|
t.Run("configured policy allows only configured values", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rule := &CompiledRule{
|
|
AllowedPolicy: map[corev1.PullPolicy]struct{}{
|
|
corev1.PullNever: {},
|
|
},
|
|
}
|
|
|
|
if !rule.AllowsPullPolicy(corev1.PullNever) {
|
|
t.Fatal("expected PullNever to be allowed")
|
|
}
|
|
|
|
if rule.AllowsPullPolicy(corev1.PullIfNotPresent) {
|
|
t.Fatal("expected PullIfNotPresent to be rejected")
|
|
}
|
|
|
|
if rule.AllowsPullPolicy("") {
|
|
t.Fatal("expected empty pull policy to be rejected")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheHashRules(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("same rules produce same hash", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
a := []rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullNever, corev1.PullIfNotPresent),
|
|
registry("ghcr.io/.*"),
|
|
}
|
|
|
|
b := []rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullIfNotPresent, corev1.PullNever),
|
|
registry("ghcr.io/.*"),
|
|
}
|
|
|
|
hashA := c.HashRules(a)
|
|
hashB := c.HashRules(b)
|
|
|
|
if hashA != hashB {
|
|
t.Fatalf("expected equal hash, got %q and %q", hashA, hashB)
|
|
}
|
|
})
|
|
|
|
t.Run("different expression produces different hash", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
hashA := c.HashRules([]rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
})
|
|
|
|
hashB := c.HashRules([]rules.OCIRegistry{
|
|
registry("ghcr.io/.*"),
|
|
})
|
|
|
|
if hashA == hashB {
|
|
t.Fatalf("expected different hashes, got %q", hashA)
|
|
}
|
|
})
|
|
|
|
t.Run("different negate value produces different hash", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
hashA := c.HashRules([]rules.OCIRegistry{
|
|
registryWithExpression(runtime.ExpressionRegex{
|
|
Expression: "trusted/.*",
|
|
Negate: false,
|
|
}),
|
|
})
|
|
|
|
hashB := c.HashRules([]rules.OCIRegistry{
|
|
registryWithExpression(runtime.ExpressionRegex{
|
|
Expression: "trusted/.*",
|
|
Negate: true,
|
|
}),
|
|
})
|
|
|
|
if hashA == hashB {
|
|
t.Fatalf("expected different hashes, got %q", hashA)
|
|
}
|
|
})
|
|
|
|
t.Run("different policy produces different hash", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
hashA := c.HashRules([]rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullNever),
|
|
})
|
|
|
|
hashB := c.HashRules([]rules.OCIRegistry{
|
|
registryWithPolicy("harbor/.*", corev1.PullIfNotPresent),
|
|
})
|
|
|
|
if hashA == hashB {
|
|
t.Fatalf("expected different hashes, got %q", hashA)
|
|
}
|
|
})
|
|
|
|
t.Run("rule order affects hash", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
hashA := c.HashRules([]rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
registry("ghcr.io/.*"),
|
|
})
|
|
|
|
hashB := c.HashRules([]rules.OCIRegistry{
|
|
registry("ghcr.io/.*"),
|
|
registry("harbor/.*"),
|
|
})
|
|
|
|
if hashA == hashB {
|
|
t.Fatalf("expected different hashes because rule order matters, got %q", hashA)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheHasResetPruneActive(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
specA := []rules.OCIRegistry{
|
|
registry("harbor/.*"),
|
|
}
|
|
|
|
specB := []rules.OCIRegistry{
|
|
registry("ghcr.io/.*"),
|
|
}
|
|
|
|
idA := c.HashRules(specA)
|
|
idB := c.HashRules(specB)
|
|
|
|
if c.Has(idA) {
|
|
t.Fatal("expected cache not to have idA before build")
|
|
}
|
|
|
|
if _, _, err := c.GetOrBuild(specA); err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if _, _, err := c.GetOrBuild(specB); err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if !c.Has(idA) {
|
|
t.Fatal("expected cache to have idA")
|
|
}
|
|
|
|
if !c.Has(idB) {
|
|
t.Fatal("expected cache to have idB")
|
|
}
|
|
|
|
if got := c.Stats(); got != 2 {
|
|
t.Fatalf("expected two cached rulesets, got %d", got)
|
|
}
|
|
|
|
removed := c.PruneActive(map[string]struct{}{
|
|
idA: {},
|
|
})
|
|
|
|
if removed != 1 {
|
|
t.Fatalf("expected one removed ruleset, got %d", removed)
|
|
}
|
|
|
|
if !c.Has(idA) {
|
|
t.Fatal("expected cache to retain idA")
|
|
}
|
|
|
|
if c.Has(idB) {
|
|
t.Fatal("expected cache to prune idB")
|
|
}
|
|
|
|
c.Reset()
|
|
|
|
if got := c.Stats(); got != 0 {
|
|
t.Fatalf("expected cache to be empty after reset, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheNilReceivers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var c *RegistryRuleSetCache
|
|
|
|
if got := c.Stats(); got != 0 {
|
|
t.Fatalf("expected nil cache stats to be 0, got %d", got)
|
|
}
|
|
|
|
if c.Has("missing") {
|
|
t.Fatal("expected nil cache Has to be false")
|
|
}
|
|
|
|
if removed := c.PruneActive(nil); removed != 0 {
|
|
t.Fatalf("expected nil cache prune to remove 0, got %d", removed)
|
|
}
|
|
|
|
c.Reset()
|
|
|
|
if _, _, err := c.GetOrBuild([]rules.OCIRegistry{registry("harbor/.*")}); err == nil {
|
|
t.Fatal("expected nil cache GetOrBuild error")
|
|
}
|
|
|
|
if _, err := c.MatchRuleSet(&RuleSet{}, "harbor/app:1", corev1.PullAlways); err == nil {
|
|
t.Fatal("expected nil cache MatchRuleSet error")
|
|
}
|
|
|
|
if _, err := c.MatchReference(&RuleSet{}, "harbor/app:1"); err == nil {
|
|
t.Fatal("expected nil cache MatchReference error")
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheNilRegexCache(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := &RegistryRuleSetCache{
|
|
rs: make(map[string]*RuleSet),
|
|
}
|
|
|
|
if _, err := c.buildRuleSet("id", []rules.OCIRegistry{registry("harbor/.*")}); err == nil {
|
|
t.Fatal("expected nil regex cache build error")
|
|
}
|
|
|
|
if _, err := c.MatchRuleSet(&RuleSet{}, "harbor/app:1", corev1.PullAlways); err == nil {
|
|
t.Fatal("expected nil regex cache MatchRuleSet error")
|
|
}
|
|
|
|
if _, err := c.MatchReference(&RuleSet{}, "harbor/app:1"); err == nil {
|
|
t.Fatal("expected nil regex cache MatchReference error")
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheMatchNilRuleSet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
got, err := c.MatchRuleSet(nil, "harbor/app:1", corev1.PullAlways)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if got != nil {
|
|
t.Fatalf("expected nil match, got %#v", got)
|
|
}
|
|
|
|
got, err = c.MatchReference(nil, "harbor/app:1")
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if got != nil {
|
|
t.Fatalf("expected nil match, got %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestRegistryRuleSetCacheInsertForTest(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewRegistryRuleSetCache(nil)
|
|
|
|
c.insertForTest("test-id")
|
|
|
|
if !c.Has("test-id") {
|
|
t.Fatal("expected inserted test id to exist")
|
|
}
|
|
}
|
|
|
|
func registry(expression string) rules.OCIRegistry {
|
|
return rules.OCIRegistry{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
ExpressionRegex: runtime.ExpressionRegex{
|
|
Expression: expression,
|
|
Negate: false,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func registryWithPolicy(expression string, policies ...corev1.PullPolicy) rules.OCIRegistry {
|
|
return rules.OCIRegistry{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
ExpressionRegex: runtime.ExpressionRegex{
|
|
Expression: expression,
|
|
Negate: false,
|
|
},
|
|
},
|
|
Policy: policies,
|
|
}
|
|
}
|
|
|
|
func registryWithExpression(expression runtime.ExpressionRegex) rules.OCIRegistry {
|
|
return rules.OCIRegistry{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
ExpressionRegex: expression,
|
|
},
|
|
}
|
|
}
|
|
|
|
func registryWithExpressionAndPolicy(
|
|
expression runtime.ExpressionRegex,
|
|
policies ...corev1.PullPolicy,
|
|
) rules.OCIRegistry {
|
|
return rules.OCIRegistry{
|
|
ExpressionMatch: runtime.ExpressionMatch{
|
|
ExpressionRegex: expression,
|
|
},
|
|
Policy: policies,
|
|
}
|
|
}
|