mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
chore(store*): add reduce complexity and improve code coverage (#5568)
* chore(store*): add reduce complexity and improve code coverage * docs(store.go): reasoning for helper function * style(store): standardized order of args * chore: gofmt auto-format source/store.go for lint compliance
This commit is contained in:
parent
93c76553e7
commit
e467e60335
@ -40,7 +40,7 @@ linters:
|
||||
- name: confusing-naming
|
||||
disabled: true
|
||||
cyclop: # Lower cyclomatic complexity threshold after the max complexity is lowered
|
||||
max-complexity: 51
|
||||
max-complexity: 44
|
||||
testifylint:
|
||||
# Enable all checkers (https://github.com/Antonboom/testifylint#checkers).
|
||||
# Default: false
|
||||
|
466
source/store.go
466
source/store.go
@ -45,6 +45,20 @@ import (
|
||||
var ErrSourceNotFound = errors.New("source not found")
|
||||
|
||||
// Config holds shared configuration options for all Sources.
|
||||
// This struct centralizes all source-related configuration to avoid parameter proliferation
|
||||
// in individual source constructors. It follows the configuration pattern where a single
|
||||
// config object is passed rather than individual parameters.
|
||||
//
|
||||
// Common Configuration Fields:
|
||||
// - Namespace: Target namespace for source operations
|
||||
// - AnnotationFilter: Filter sources by annotation patterns
|
||||
// - LabelFilter: Filter sources by label selectors
|
||||
// - FQDNTemplate: Template for generating fully qualified domain names
|
||||
// - CombineFQDNAndAnnotation: Whether to combine FQDN template with annotations
|
||||
// - IgnoreHostnameAnnotation: Whether to ignore hostname annotations
|
||||
//
|
||||
// The config is created from externaldns.Config via NewSourceConfig() which handles
|
||||
// type conversions and validation.
|
||||
type Config struct {
|
||||
Namespace string
|
||||
AnnotationFilter string
|
||||
@ -135,7 +149,21 @@ func NewSourceConfig(cfg *externaldns.Config) *Config {
|
||||
}
|
||||
}
|
||||
|
||||
// ClientGenerator provides clients
|
||||
// ClientGenerator provides clients for various Kubernetes APIs and external services.
|
||||
// This interface abstracts client creation and enables dependency injection for testing.
|
||||
// It uses the singleton pattern to ensure only one instance of each client is created
|
||||
// and reused across multiple source instances.
|
||||
//
|
||||
// Supported Client Types:
|
||||
// - KubeClient: Standard Kubernetes API client
|
||||
// - GatewayClient: Gateway API client for Gateway resources
|
||||
// - IstioClient: Istio service mesh client
|
||||
// - CloudFoundryClient: CloudFoundry platform client
|
||||
// - DynamicKubernetesClient: Dynamic client for custom resources
|
||||
// - OpenShiftClient: OpenShift-specific client for Route resources
|
||||
//
|
||||
// The singleton behavior is implemented in SingletonClientGenerator which uses
|
||||
// sync.Once to guarantee single initialization of each client type.
|
||||
type ClientGenerator interface {
|
||||
KubeClient() (kubernetes.Interface, error)
|
||||
GatewayClient() (gateway.Interface, error)
|
||||
@ -145,8 +173,17 @@ type ClientGenerator interface {
|
||||
OpenShiftClient() (openshift.Interface, error)
|
||||
}
|
||||
|
||||
// SingletonClientGenerator stores provider clients and guarantees that only one instance of client
|
||||
// will be generated
|
||||
// SingletonClientGenerator stores provider clients and guarantees that only one instance of each client
|
||||
// will be generated throughout the application lifecycle.
|
||||
//
|
||||
// Thread Safety: Uses sync.Once for each client type to ensure thread-safe initialization.
|
||||
// This is important because external-dns may create multiple sources concurrently.
|
||||
//
|
||||
// Memory Efficiency: Prevents creating multiple instances of expensive client objects
|
||||
// that maintain their own connection pools and caches.
|
||||
//
|
||||
// Configuration: Clients are configured using KubeConfig, APIServerURL, and RequestTimeout
|
||||
// which are set during SingletonClientGenerator initialization.
|
||||
type SingletonClientGenerator struct {
|
||||
KubeConfig string
|
||||
APIServerURL string
|
||||
@ -261,33 +298,47 @@ func ByNames(ctx context.Context, p ClientGenerator, names []string, cfg *Config
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
// BuildWithConfig allows generating a Source implementation from the shared config
|
||||
// BuildWithConfig creates a Source implementation using the factory pattern.
|
||||
// This function serves as the central registry for all available source types.
|
||||
//
|
||||
// Source Selection: Uses a string identifier to determine which source type to create.
|
||||
// This allows for runtime configuration and easy extension with new source types.
|
||||
//
|
||||
// Error Handling: Returns ErrSourceNotFound for unsupported source types,
|
||||
// allowing callers to handle unknown sources gracefully.
|
||||
//
|
||||
// Supported Source Types:
|
||||
// - "node": Kubernetes nodes
|
||||
// - "service": Kubernetes services
|
||||
// - "ingress": Kubernetes ingresses
|
||||
// - "pod": Kubernetes pods
|
||||
// - "gateway-*": Gateway API resources (httproute, grpcroute, tlsroute, tcproute, udproute)
|
||||
// - "istio-*": Istio resources (gateway, virtualservice)
|
||||
// - "cloudfoundry": CloudFoundry applications
|
||||
// - "ambassador-host": Ambassador Host resources
|
||||
// - "contour-httpproxy": Contour HTTPProxy resources
|
||||
// - "gloo-proxy": Gloo proxy resources
|
||||
// - "traefik-proxy": Traefik proxy resources
|
||||
// - "openshift-route": OpenShift Route resources
|
||||
// - "crd": Custom Resource Definitions
|
||||
// - "skipper-routegroup": Skipper RouteGroup resources
|
||||
// - "kong-tcpingress": Kong TCP Ingress resources
|
||||
// - "f5-*": F5 resources (virtualserver, transportserver)
|
||||
// - "fake": Fake source for testing
|
||||
// - "connector": Connector source for external systems
|
||||
//
|
||||
// Design Note: Gateway API sources use a different pattern (direct constructor calls)
|
||||
// because they have simpler initialization requirements.
|
||||
func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
switch source {
|
||||
case "node":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewNodeSource(ctx, client, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.LabelFilter, cfg.ExposeInternalIPv6, cfg.ExcludeUnschedulable, cfg.CombineFQDNAndAnnotation)
|
||||
return buildNodeSource(ctx, p, cfg)
|
||||
case "service":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServiceSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter, cfg.ResolveLoadBalancerHostname, cfg.ListenEndpointEvents, cfg.ExposeInternalIPv6)
|
||||
return buildServiceSource(ctx, p, cfg)
|
||||
case "ingress":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIngressSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter, cfg.IngressClassNames)
|
||||
return buildIngressSource(ctx, p, cfg)
|
||||
case "pod":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPodSource(ctx, client, cfg.Namespace, cfg.Compatibility, cfg.IgnoreNonHostNetworkPods, cfg.PodSourceDomain, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation)
|
||||
return buildPodSource(ctx, p, cfg)
|
||||
case "gateway-httproute":
|
||||
return NewGatewayHTTPRouteSource(p, cfg)
|
||||
case "gateway-grpcroute":
|
||||
@ -299,133 +350,274 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg
|
||||
case "gateway-udproute":
|
||||
return NewGatewayUDPRouteSource(p, cfg)
|
||||
case "istio-gateway":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
istioClient, err := p.IstioClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIstioGatewaySource(ctx, kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
return buildIstioGatewaySource(ctx, p, cfg)
|
||||
case "istio-virtualservice":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
istioClient, err := p.IstioClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIstioVirtualServiceSource(ctx, kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
return buildIstioVirtualServiceSource(ctx, p, cfg)
|
||||
case "cloudfoundry":
|
||||
cfClient, err := p.CloudFoundryClient(cfg.CFAPIEndpoint, cfg.CFUsername, cfg.CFPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCloudFoundrySource(cfClient)
|
||||
return buildCloudFoundrySource(ctx, p, cfg)
|
||||
case "ambassador-host":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewAmbassadorHostSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter, cfg.LabelFilter)
|
||||
return buildAmbassadorHostSource(ctx, p, cfg)
|
||||
case "contour-httpproxy":
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewContourHTTPProxySource(ctx, dynamicClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
return buildContourHTTPProxySource(ctx, p, cfg)
|
||||
case "gloo-proxy":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewGlooSource(dynamicClient, kubernetesClient, cfg.GlooNamespaces)
|
||||
return buildGlooProxySource(ctx, p, cfg)
|
||||
case "traefik-proxy":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTraefikSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter, cfg.IgnoreHostnameAnnotation, cfg.TraefikDisableLegacy, cfg.TraefikDisableNew)
|
||||
return buildTraefikProxySource(ctx, p, cfg)
|
||||
case "openshift-route":
|
||||
ocpClient, err := p.OpenShiftClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewOcpRouteSource(ctx, ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter, cfg.OCPRouterName)
|
||||
return buildOpenShiftRouteSource(ctx, p, cfg)
|
||||
case "fake":
|
||||
return NewFakeSource(cfg.FQDNTemplate)
|
||||
case "connector":
|
||||
return NewConnectorSource(cfg.ConnectorServer)
|
||||
case "crd":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crdClient, scheme, err := NewCRDClientForAPIVersionKind(client, cfg.KubeConfig, cfg.APIServerURL, cfg.CRDSourceAPIVersion, cfg.CRDSourceKind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCRDSource(crdClient, cfg.Namespace, cfg.CRDSourceKind, cfg.AnnotationFilter, cfg.LabelFilter, scheme, cfg.UpdateEvents)
|
||||
return buildCRDSource(ctx, p, cfg)
|
||||
case "skipper-routegroup":
|
||||
apiServerURL := cfg.APIServerURL
|
||||
tokenPath := ""
|
||||
token := ""
|
||||
restConfig, err := GetRestConfig(cfg.KubeConfig, cfg.APIServerURL)
|
||||
if err == nil {
|
||||
apiServerURL = restConfig.Host
|
||||
tokenPath = restConfig.BearerTokenFile
|
||||
token = restConfig.BearerToken
|
||||
}
|
||||
return NewRouteGroupSource(cfg.RequestTimeout, token, tokenPath, apiServerURL, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.SkipperRouteGroupVersion, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
return buildSkipperRouteGroupSource(ctx, cfg)
|
||||
case "kong-tcpingress":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewKongTCPIngressSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter, cfg.IgnoreHostnameAnnotation)
|
||||
return buildKongTCPIngressSource(ctx, p, cfg)
|
||||
case "f5-virtualserver":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewF5VirtualServerSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter)
|
||||
return buildF5VirtualServerSource(ctx, p, cfg)
|
||||
case "f5-transportserver":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewF5TransportServerSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter)
|
||||
return buildF5TransportServerSource(ctx, p, cfg)
|
||||
}
|
||||
|
||||
return nil, ErrSourceNotFound
|
||||
}
|
||||
|
||||
// Source Builder Functions
|
||||
//
|
||||
// The following functions follow a standardized pattern for creating source instances.
|
||||
// This standardization improves code consistency, maintainability, and readability.
|
||||
//
|
||||
// Standardized Function Signature Pattern:
|
||||
//
|
||||
// func buildXXXSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error)
|
||||
//
|
||||
// Standardized Constructor Parameter Pattern (where applicable):
|
||||
// 1. ctx (context.Context) - Always first when supported by the source constructor
|
||||
// 2. client(s) (kubernetes.Interface, dynamic.Interface, etc.) - Kubernetes clients
|
||||
// 3. namespace (string) - Target namespace for the source
|
||||
// 4. annotationFilter (string) - Filter for annotations
|
||||
// 5. labelFilter (labels.Selector) - Filter for labels (when applicable)
|
||||
// 6. fqdnTemplate (string) - FQDN template for DNS record generation
|
||||
// 7. combineFQDNAndAnnotation (bool) - Whether to combine FQDN template with annotations
|
||||
// 8. ...other parameters - Source-specific parameters in logical order
|
||||
//
|
||||
// Design Principles:
|
||||
// - Each source type has its own specific requirements and dependencies
|
||||
// - Separating build functions allows for clearer code organization and easier maintenance
|
||||
// - Individual functions enable straightforward error handling and independent testing
|
||||
// - Modularity makes it easier to add new source types or modify existing ones
|
||||
// - Consistent parameter ordering reduces cognitive load when working with multiple sources
|
||||
//
|
||||
// Note: Some sources may deviate from the standard pattern due to their unique requirements
|
||||
// (e.g., RouteGroupSource doesn't use ClientGenerator, GlooSource doesn't accept context)
|
||||
// buildNodeSource creates a Node source for exposing node information as DNS records.
|
||||
// Follows standard pattern: ctx, client, annotationFilter, fqdnTemplate, labelFilter, ...other
|
||||
func buildNodeSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewNodeSource(ctx, client, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.LabelFilter, cfg.ExposeInternalIPv6, cfg.ExcludeUnschedulable, cfg.CombineFQDNAndAnnotation)
|
||||
}
|
||||
|
||||
// buildServiceSource creates a Service source for exposing Kubernetes services as DNS records.
|
||||
// Follows standard pattern: ctx, client, namespace, annotationFilter, fqdnTemplate, ...other
|
||||
func buildServiceSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServiceSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter, cfg.ResolveLoadBalancerHostname, cfg.ListenEndpointEvents, cfg.ExposeInternalIPv6)
|
||||
}
|
||||
|
||||
// buildIngressSource creates an Ingress source for exposing Kubernetes ingresses as DNS records.
|
||||
// Follows standard pattern: ctx, client, namespace, annotationFilter, fqdnTemplate, ...other
|
||||
func buildIngressSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIngressSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter, cfg.IngressClassNames)
|
||||
}
|
||||
|
||||
// buildPodSource creates a Pod source for exposing Kubernetes pods as DNS records.
|
||||
// Follows standard pattern: ctx, client, namespace, ...other (no annotation/label filters)
|
||||
func buildPodSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPodSource(ctx, client, cfg.Namespace, cfg.Compatibility, cfg.IgnoreNonHostNetworkPods, cfg.PodSourceDomain, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation)
|
||||
}
|
||||
|
||||
// buildIstioGatewaySource creates an Istio Gateway source for exposing Istio gateways as DNS records.
|
||||
// Requires both Kubernetes and Istio clients. Follows standard parameter pattern.
|
||||
func buildIstioGatewaySource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
istioClient, err := p.IstioClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIstioGatewaySource(ctx, kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
}
|
||||
|
||||
// buildIstioVirtualServiceSource creates an Istio VirtualService source for exposing virtual services as DNS records.
|
||||
// Requires both Kubernetes and Istio clients. Follows standard parameter pattern.
|
||||
func buildIstioVirtualServiceSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
istioClient, err := p.IstioClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIstioVirtualServiceSource(ctx, kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
}
|
||||
|
||||
// buildCloudFoundrySource creates a CloudFoundry source for exposing CF applications as DNS records.
|
||||
// Uses CloudFoundry client instead of Kubernetes client. Simple constructor with minimal parameters.
|
||||
func buildCloudFoundrySource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
cfClient, err := p.CloudFoundryClient(cfg.CFAPIEndpoint, cfg.CFUsername, cfg.CFPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCloudFoundrySource(cfClient)
|
||||
}
|
||||
|
||||
func buildAmbassadorHostSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewAmbassadorHostSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter, cfg.LabelFilter)
|
||||
}
|
||||
|
||||
func buildContourHTTPProxySource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewContourHTTPProxySource(ctx, dynamicClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
}
|
||||
|
||||
// buildGlooProxySource creates a Gloo source for exposing Gloo proxies as DNS records.
|
||||
// Requires both dynamic and standard Kubernetes clients.
|
||||
// Note: Does not accept context parameter in constructor (legacy design).
|
||||
func buildGlooProxySource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewGlooSource(dynamicClient, kubernetesClient, cfg.GlooNamespaces)
|
||||
}
|
||||
|
||||
func buildTraefikProxySource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTraefikSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter, cfg.IgnoreHostnameAnnotation, cfg.TraefikDisableLegacy, cfg.TraefikDisableNew)
|
||||
}
|
||||
|
||||
func buildOpenShiftRouteSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
ocpClient, err := p.OpenShiftClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewOcpRouteSource(ctx, ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter, cfg.OCPRouterName)
|
||||
}
|
||||
|
||||
// buildCRDSource creates a CRD source for exposing custom resources as DNS records.
|
||||
// Uses a specialized CRD client created via NewCRDClientForAPIVersionKind.
|
||||
// Parameter order: crdClient, namespace, kind, annotationFilter, labelFilter, scheme, updateEvents
|
||||
func buildCRDSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crdClient, scheme, err := NewCRDClientForAPIVersionKind(client, cfg.KubeConfig, cfg.APIServerURL, cfg.CRDSourceAPIVersion, cfg.CRDSourceKind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCRDSource(crdClient, cfg.Namespace, cfg.CRDSourceKind, cfg.AnnotationFilter, cfg.LabelFilter, scheme, cfg.UpdateEvents)
|
||||
}
|
||||
|
||||
// buildSkipperRouteGroupSource creates a Skipper RouteGroup source for exposing route groups as DNS records.
|
||||
// Special case: Does not use ClientGenerator pattern, instead manages its own authentication.
|
||||
// Retrieves bearer token from REST config for API server authentication.
|
||||
func buildSkipperRouteGroupSource(ctx context.Context, cfg *Config) (Source, error) {
|
||||
apiServerURL := cfg.APIServerURL
|
||||
tokenPath := ""
|
||||
token := ""
|
||||
restConfig, err := GetRestConfig(cfg.KubeConfig, cfg.APIServerURL)
|
||||
if err == nil {
|
||||
apiServerURL = restConfig.Host
|
||||
tokenPath = restConfig.BearerTokenFile
|
||||
token = restConfig.BearerToken
|
||||
}
|
||||
return NewRouteGroupSource(cfg.RequestTimeout, token, tokenPath, apiServerURL, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.SkipperRouteGroupVersion, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
}
|
||||
|
||||
func buildKongTCPIngressSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewKongTCPIngressSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter, cfg.IgnoreHostnameAnnotation)
|
||||
}
|
||||
|
||||
func buildF5VirtualServerSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewF5VirtualServerSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter)
|
||||
}
|
||||
|
||||
func buildF5TransportServerSource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewF5TransportServerSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter)
|
||||
}
|
||||
|
||||
// instrumentedRESTConfig creates a REST config with request instrumentation for monitoring.
|
||||
// Adds HTTP transport wrapper for Prometheus metrics collection and request timeout configuration.
|
||||
//
|
||||
// Path Processing: Simplifies URL paths for metrics by taking the last segment,
|
||||
// reducing cardinality of metric labels for better performance.
|
||||
//
|
||||
// Timeout: Applies the specified request timeout to prevent hanging requests.
|
||||
func instrumentedRESTConfig(kubeConfig, apiServerURL string, requestTimeout time.Duration) (*rest.Config, error) {
|
||||
config, err := GetRestConfig(kubeConfig, apiServerURL)
|
||||
if err != nil {
|
||||
@ -443,8 +635,16 @@ func instrumentedRESTConfig(kubeConfig, apiServerURL string, requestTimeout time
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetRestConfig returns the rest clients config to get automatically
|
||||
// data if you run inside a cluster or by passing flags.
|
||||
// GetRestConfig returns the REST client configuration for Kubernetes API access.
|
||||
// Supports both in-cluster and external cluster configurations.
|
||||
//
|
||||
// Configuration Priority:
|
||||
// 1. If kubeConfig is empty, tries the recommended home file (~/.kube/config)
|
||||
// 2. If kubeConfig is still empty, uses in-cluster service account
|
||||
// 3. Otherwise, uses the specified kubeConfig file
|
||||
//
|
||||
// API Server Override: The apiServerURL parameter can override the server URL
|
||||
// from the kubeconfig file, useful for proxy scenarios or custom endpoints.
|
||||
func GetRestConfig(kubeConfig, apiServerURL string) (*rest.Config, error) {
|
||||
if kubeConfig == "" {
|
||||
if _, err := os.Stat(clientcmd.RecommendedHomeFile); err == nil {
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
istioclient "istio.io/client-go/pkg/clientset/versioned"
|
||||
istiofake "istio.io/client-go/pkg/clientset/versioned/fake"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
@ -239,3 +240,36 @@ func (suite *ByNamesTestSuite) TestDynamicKubernetesClientFails() {
|
||||
func TestByNames(t *testing.T) {
|
||||
suite.Run(t, new(ByNamesTestSuite))
|
||||
}
|
||||
|
||||
type minimalMockClientGenerator struct{}
|
||||
|
||||
var errMock = errors.New("mock not implemented")
|
||||
|
||||
func (m *minimalMockClientGenerator) KubeClient() (kubernetes.Interface, error) { return nil, errMock }
|
||||
func (m *minimalMockClientGenerator) GatewayClient() (gateway.Interface, error) { return nil, errMock }
|
||||
func (m *minimalMockClientGenerator) IstioClient() (istioclient.Interface, error) {
|
||||
return nil, errMock
|
||||
}
|
||||
func (m *minimalMockClientGenerator) CloudFoundryClient(string, string, string) (*cfclient.Client, error) {
|
||||
return nil, errMock
|
||||
}
|
||||
func (m *minimalMockClientGenerator) DynamicKubernetesClient() (dynamic.Interface, error) {
|
||||
return nil, errMock
|
||||
}
|
||||
func (m *minimalMockClientGenerator) OpenShiftClient() (openshift.Interface, error) {
|
||||
return nil, errMock
|
||||
}
|
||||
|
||||
func TestBuildWithConfig_InvalidSource(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := &minimalMockClientGenerator{}
|
||||
cfg := &Config{LabelFilter: labels.NewSelector()}
|
||||
|
||||
src, err := BuildWithConfig(ctx, "not-a-source", p, cfg)
|
||||
if src != nil {
|
||||
t.Errorf("expected nil source for invalid type, got: %v", src)
|
||||
}
|
||||
if !errors.Is(err, ErrSourceNotFound) {
|
||||
t.Errorf("expected ErrSourceNotFound, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user