improvements jan2025 (#6)

* feat(controller): add lazy watcher, improve resource usage and add pattern validation

- [x] Add cache sync health check for readiness probe verification
- [x] Create namespace lister with API reader support for fresh label queries
- [x] Add pattern validation with warning logs for invalid glob patterns
- [x] Implement lazy watcher initialization mode to scan for active resources
- [x] Add requeue delay to namespace reconciler for cache settlement
- [x] Replace custom containsString with slices.Contains from stdlib
- [x] Add structured logging context to reconcilers (kind, group, version)
- [x] Improve error variable naming for clarity in nested conditions
- [x] Add nil-safe label access in namespace reconciler setup
- [x] Add APIReader to namespace and source reconcilers for fresh data
- [x] Improve type assertions with proper error handling in mirror operations
- [x] Reorder struct fields for consistency and readability
- [x] Add comprehensive pattern validation tests and validation API

* feat(controller): add lazy watcher, improve resource usage and add pattern validation

- [x] Add circuit breaker for reconciliation failure tracking and prevention
- [x] Implement granular registration state tracking (not-registered, source-only, fully-registered)
- [x] Add lazy controller initialization for active resource types only
- [x] Consolidate namespace listing into single API call for efficiency
- [x] Add mirror creation verification to catch webhook rejections
- [x] Implement high-cardinality resource detection and warnings
- [x] Add source deletion check in mirror reconciler to prevent races
- [x] Preserve transformation annotations on errors in mirror reconciliation
- [x] Expand constants documentation with labels vs annotations design rationale
- [x] Add comprehensive test coverage for circuit breaker and registration states
- [x] Add mutation-safety tests for hash computation

* fixup! feat(controller): add lazy watcher, improve resource usage and add pattern validation
This commit is contained in:
2026-01-14 13:07:11 +00:00
committed by GitHub
parent 4f8e2783cf
commit 096dca47d1
22 changed files with 1937 additions and 266 deletions
+67
View File
@@ -2,12 +2,79 @@
package filter
import (
"fmt"
"path/filepath"
"strings"
"github.com/lukaszraczylo/kubemirror/pkg/constants"
)
// PatternValidationResult contains the result of validating a pattern.
type PatternValidationResult struct {
Error error
Pattern string
Valid bool
}
// ValidatePattern checks if a glob pattern is syntactically valid.
// Returns an error if the pattern cannot be compiled by filepath.Match.
func ValidatePattern(pattern string) error {
// Empty pattern is invalid
if pattern == "" {
return fmt.Errorf("empty pattern")
}
// Special keywords are always valid
if pattern == constants.TargetNamespacesAll || pattern == constants.TargetNamespacesAllLabeled {
return nil
}
// Use filepath.Match with a test string to validate pattern syntax
// We use "test" as a dummy value - we only care about the error
_, err := filepath.Match(pattern, "test")
if err != nil {
return fmt.Errorf("invalid glob pattern %q: %w", pattern, err)
}
return nil
}
// ValidatePatterns validates a list of patterns and returns results for each.
// Returns a slice of validation results and a boolean indicating if all patterns are valid.
func ValidatePatterns(patterns []string) ([]PatternValidationResult, bool) {
if len(patterns) == 0 {
return nil, true
}
results := make([]PatternValidationResult, len(patterns))
allValid := true
for i, pattern := range patterns {
err := ValidatePattern(pattern)
results[i] = PatternValidationResult{
Pattern: pattern,
Valid: err == nil,
Error: err,
}
if err != nil {
allValid = false
}
}
return results, allValid
}
// InvalidPatterns returns only the invalid patterns from a validation result.
func InvalidPatterns(results []PatternValidationResult) []PatternValidationResult {
var invalid []PatternValidationResult
for _, r := range results {
if !r.Valid {
invalid = append(invalid, r)
}
}
return invalid
}
// NamespaceFilter handles namespace filtering logic including patterns and exclusions.
type NamespaceFilter struct {
excludedNamespaces map[string]bool
+164
View File
@@ -592,3 +592,167 @@ func BenchmarkResolveTargetNamespaces_LargeScale(b *testing.B) {
}
})
}
// Tests for pattern validation
func TestValidatePattern(t *testing.T) {
tests := []struct {
name string
pattern string
wantErr bool
}{
{
name: "valid simple pattern",
pattern: "app-*",
wantErr: false,
},
{
name: "valid complex pattern",
pattern: "*-app-*-db",
wantErr: false,
},
{
name: "valid exact match",
pattern: "my-namespace",
wantErr: false,
},
{
name: "valid question mark pattern",
pattern: "app-?",
wantErr: false,
},
{
name: "valid character class pattern",
pattern: "app-[abc]",
wantErr: false,
},
{
name: "valid 'all' keyword",
pattern: constants.TargetNamespacesAll,
wantErr: false,
},
{
name: "valid 'all-labeled' keyword",
pattern: constants.TargetNamespacesAllLabeled,
wantErr: false,
},
{
name: "invalid unclosed bracket",
pattern: "app-[",
wantErr: true,
},
{
name: "character range pattern is valid",
pattern: "app-[z-a]",
wantErr: false, // filepath.Match accepts character ranges
},
{
name: "empty pattern is invalid",
pattern: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidatePattern(tt.pattern)
if tt.wantErr {
assert.Error(t, err, "expected error for pattern %q", tt.pattern)
} else {
assert.NoError(t, err, "unexpected error for pattern %q", tt.pattern)
}
})
}
}
func TestValidatePatterns(t *testing.T) {
tests := []struct {
name string
patterns []string
wantAllValid bool
wantInvalid int
}{
{
name: "all valid patterns",
patterns: []string{"app-*", "prod-*", "staging-db"},
wantAllValid: true,
wantInvalid: 0,
},
{
name: "empty patterns list",
patterns: []string{},
wantAllValid: true,
wantInvalid: 0,
},
{
name: "one invalid pattern",
patterns: []string{"app-*", "invalid-[", "prod-*"},
wantAllValid: false,
wantInvalid: 1,
},
{
name: "multiple invalid patterns",
patterns: []string{"invalid-[", "app-*", "bad-["},
wantAllValid: false,
wantInvalid: 2,
},
{
name: "all invalid patterns",
patterns: []string{"bad-[", "worse-["},
wantAllValid: false,
wantInvalid: 2,
},
{
name: "mixed with keywords",
patterns: []string{constants.TargetNamespacesAll, "bad-[", "app-*"},
wantAllValid: false,
wantInvalid: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
results, allValid := ValidatePatterns(tt.patterns)
assert.Equal(t, tt.wantAllValid, allValid, "allValid mismatch")
invalidPatterns := InvalidPatterns(results)
assert.Equal(t, tt.wantInvalid, len(invalidPatterns), "invalid count mismatch")
// Verify all invalid patterns have errors
for _, invalid := range invalidPatterns {
assert.False(t, invalid.Valid)
assert.NotNil(t, invalid.Error)
}
})
}
}
func TestInvalidPatterns(t *testing.T) {
t.Run("filters only invalid patterns", func(t *testing.T) {
results := []PatternValidationResult{
{Pattern: "app-*", Valid: true, Error: nil},
{Pattern: "bad-[", Valid: false, Error: fmt.Errorf("invalid")},
{Pattern: "prod-*", Valid: true, Error: nil},
{Pattern: "worse-[", Valid: false, Error: fmt.Errorf("invalid")},
}
invalid := InvalidPatterns(results)
assert.Len(t, invalid, 2)
assert.Equal(t, "bad-[", invalid[0].Pattern)
assert.Equal(t, "worse-[", invalid[1].Pattern)
})
t.Run("returns nil for empty input", func(t *testing.T) {
invalid := InvalidPatterns(nil)
assert.Nil(t, invalid)
})
t.Run("returns nil for all valid patterns", func(t *testing.T) {
results := []PatternValidationResult{
{Pattern: "app-*", Valid: true, Error: nil},
{Pattern: "prod-*", Valid: true, Error: nil},
}
invalid := InvalidPatterns(results)
assert.Nil(t, invalid)
})
}