mirror of
https://github.com/lukaszraczylo/kubemirror.git
synced 2026-07-08 12:44:41 +00:00
initial commit
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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: ""})
|
||||
}
|
||||
Reference in New Issue
Block a user