initial commit

This commit is contained in:
2025-12-25 22:10:57 +00:00
commit 8adb52608f
46 changed files with 7570 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
// Package config provides configuration for the kubemirror controller.
package config
import (
"time"
)
// Config holds all configuration for the controller.
type Config struct {
// MetricsBindAddress is the address for the metrics endpoint
MetricsBindAddress string
// HealthProbeBindAddress is the address for health probes
HealthProbeBindAddress string
// WatchNamespaces is the list of namespaces to watch (empty = all namespaces)
WatchNamespaces []string
// ExcludedNamespaces is the list of namespaces to never mirror to
ExcludedNamespaces []string
// MirroredResourceTypes is the list of resource types to mirror
// If empty, defaults to Secret and ConfigMap only
MirroredResourceTypes []ResourceType
// DeniedResourceTypes is the deny-list of resource types (by name, for backward compatibility)
DeniedResourceTypes []string
// LeaderElection configuration
LeaderElection LeaderElectionConfig
// ReconcileInterval is how often to re-check all resources
ReconcileInterval time.Duration
// WorkerThreads is the number of concurrent reconciliation workers
WorkerThreads int
// RateLimitBurst is the burst capacity for rate limiting
RateLimitBurst int
// MemoryLimitMB is the memory limit in megabytes
MemoryLimitMB int
// DebounceDuration is the debounce window for source updates
DebounceDuration time.Duration
// MaxTargetsPerResource is the maximum number of target namespaces per resource
MaxTargetsPerResource int
// RateLimitQPS is the maximum queries per second to the API server
RateLimitQPS float32
// RequireNamespaceOptIn requires namespaces to have label for "all" mirrors
RequireNamespaceOptIn bool
// EnableAllKeyword enables the "all" keyword for target namespaces
EnableAllKeyword bool
// DryRun mode logs what would happen without actually making changes
DryRun bool
}
// LeaderElectionConfig holds leader election settings.
type LeaderElectionConfig struct {
// ResourceName is the name of the leader election resource
ResourceName string
// ResourceNamespace is the namespace for the leader election resource
ResourceNamespace string
// LeaseDuration is the lease duration
LeaseDuration time.Duration
// RenewDeadline is the renew deadline
RenewDeadline time.Duration
// RetryPeriod is the retry period
RetryPeriod time.Duration
// Enabled enables leader election
Enabled bool
}
// Validate checks if the configuration is valid.
func (c *Config) Validate() error {
// Add validation logic if needed
return nil
}
+98
View File
@@ -0,0 +1,98 @@
package config
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// ResourceType defines a Kubernetes resource type to mirror.
type ResourceType struct {
Group string
Version string
Kind string
}
// GroupVersionKind returns the GVK for this resource type.
func (r ResourceType) GroupVersionKind() schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: r.Group,
Version: r.Version,
Kind: r.Kind,
}
}
// String returns a string representation of the resource type.
func (r ResourceType) String() string {
if r.Group == "" {
return fmt.Sprintf("%s.%s", r.Kind, r.Version)
}
return fmt.Sprintf("%s.%s.%s", r.Kind, r.Version, r.Group)
}
// ParseResourceType parses a resource type string in the format "kind.version.group" or "kind.version".
// Examples: "Secret.v1", "Ingress.v1.networking.k8s.io", "Middleware.v1alpha1.traefik.io"
func ParseResourceType(s string) (ResourceType, error) {
parts := strings.Split(s, ".")
switch len(parts) {
case 2:
// Core resources: "Secret.v1"
return ResourceType{
Kind: parts[0],
Version: parts[1],
Group: "",
}, nil
case 3:
// Resources with group: "Ingress.v1.networking.k8s.io"
return ResourceType{
Kind: parts[0],
Version: parts[1],
Group: parts[2],
}, nil
default:
// Support more complex groups with dots: "Middleware.v1alpha1.traefik.io"
if len(parts) >= 3 {
return ResourceType{
Kind: parts[0],
Version: parts[1],
Group: strings.Join(parts[2:], "."),
}, nil
}
return ResourceType{}, fmt.Errorf("invalid resource type format: %s (expected kind.version or kind.version.group)", s)
}
}
// DefaultResourceTypes returns the default set of resource types to mirror.
func DefaultResourceTypes() []ResourceType {
return []ResourceType{
{Kind: "Secret", Version: "v1", Group: ""},
{Kind: "ConfigMap", Version: "v1", Group: ""},
}
}
// ParseResourceTypes parses a comma-separated list of resource type strings.
func ParseResourceTypes(s string) ([]ResourceType, error) {
if s == "" {
return DefaultResourceTypes(), nil
}
parts := strings.Split(s, ",")
types := make([]ResourceType, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
rt, err := ParseResourceType(part)
if err != nil {
return nil, fmt.Errorf("failed to parse resource type %q: %w", part, err)
}
types = append(types, rt)
}
return types, nil
}
+225
View File
@@ -0,0 +1,225 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestParseResourceType(t *testing.T) {
tests := []struct {
name string
input string
want ResourceType
wantErr bool
}{
{
name: "core resource - Secret",
input: "Secret.v1",
want: ResourceType{
Kind: "Secret",
Version: "v1",
Group: "",
},
},
{
name: "core resource - ConfigMap",
input: "ConfigMap.v1",
want: ResourceType{
Kind: "ConfigMap",
Version: "v1",
Group: "",
},
},
{
name: "resource with simple group",
input: "Ingress.v1.networking.k8s.io",
want: ResourceType{
Kind: "Ingress",
Version: "v1",
Group: "networking.k8s.io",
},
},
{
name: "resource with complex group",
input: "Middleware.v1alpha1.traefik.io",
want: ResourceType{
Kind: "Middleware",
Version: "v1alpha1",
Group: "traefik.io",
},
},
{
name: "CRD example",
input: "Certificate.v1.cert-manager.io",
want: ResourceType{
Kind: "Certificate",
Version: "v1",
Group: "cert-manager.io",
},
},
{
name: "invalid format - single part",
input: "Secret",
wantErr: true,
},
{
name: "empty string",
input: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseResourceType(tt.input)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
func TestResourceType_GroupVersionKind(t *testing.T) {
tests := []struct {
name string
rt ResourceType
want schema.GroupVersionKind
}{
{
name: "core resource",
rt: ResourceType{
Kind: "Secret",
Version: "v1",
Group: "",
},
want: schema.GroupVersionKind{
Kind: "Secret",
Version: "v1",
Group: "",
},
},
{
name: "resource with group",
rt: ResourceType{
Kind: "Ingress",
Version: "v1",
Group: "networking.k8s.io",
},
want: schema.GroupVersionKind{
Kind: "Ingress",
Version: "v1",
Group: "networking.k8s.io",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.rt.GroupVersionKind()
assert.Equal(t, tt.want, got)
})
}
}
func TestResourceType_String(t *testing.T) {
tests := []struct {
name string
rt ResourceType
want string
}{
{
name: "core resource",
rt: ResourceType{
Kind: "Secret",
Version: "v1",
Group: "",
},
want: "Secret.v1",
},
{
name: "resource with group",
rt: ResourceType{
Kind: "Ingress",
Version: "v1",
Group: "networking.k8s.io",
},
want: "Ingress.v1.networking.k8s.io",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.rt.String()
assert.Equal(t, tt.want, got)
})
}
}
func TestParseResourceTypes(t *testing.T) {
tests := []struct {
name string
input string
want []ResourceType
wantErr bool
}{
{
name: "empty string returns defaults",
input: "",
want: DefaultResourceTypes(),
},
{
name: "single resource type",
input: "Secret.v1",
want: []ResourceType{
{Kind: "Secret", Version: "v1", Group: ""},
},
},
{
name: "multiple resource types",
input: "Secret.v1,ConfigMap.v1,Ingress.v1.networking.k8s.io",
want: []ResourceType{
{Kind: "Secret", Version: "v1", Group: ""},
{Kind: "ConfigMap", Version: "v1", Group: ""},
{Kind: "Ingress", Version: "v1", Group: "networking.k8s.io"},
},
},
{
name: "with whitespace",
input: " Secret.v1 , ConfigMap.v1 ",
want: []ResourceType{
{Kind: "Secret", Version: "v1", Group: ""},
{Kind: "ConfigMap", Version: "v1", Group: ""},
},
},
{
name: "invalid format in list",
input: "Secret.v1,Invalid",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseResourceTypes(tt.input)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
func TestDefaultResourceTypes(t *testing.T) {
defaults := DefaultResourceTypes()
assert.Len(t, defaults, 2)
assert.Contains(t, defaults, ResourceType{Kind: "Secret", Version: "v1", Group: ""})
assert.Contains(t, defaults, ResourceType{Kind: "ConfigMap", Version: "v1", Group: ""})
}