Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion chart/templates/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ metadata:
rules:
# Controller Permissions
- apiGroups: ["traefik.io"]
resources: ["ingressroutes"]
resources: ["ingressroutes", "ingressroutetcps"]
verbs: ["get", "list", "watch"]
# Integrations
{{ if .Values.integrations.certManager.enabled }}
Expand Down
10 changes: 10 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ func main() {
os.Exit(1)
}

tcpController, err := controllers.NewIngressRouteTCPReconciler(manager.GetClient(), logger, config)
if err != nil {
logger.Error("unable to initialize ingress route tcp controller", "error", err)
os.Exit(1)
}
if err := tcpController.SetupWithManager(manager); err != nil {
logger.Error("unable to start ingress route tcp controller", "error", err)
os.Exit(1)
}

// Add health check endpoints
if err := manager.AddReadyzCheck("readyz", healthz.Ping); err != nil {
logger.Error("unable to set up ready check at /readyz", "error", err)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ require (
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
Expand All @@ -67,6 +68,7 @@ require (
github.com/rs/zerolog v1.34.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/traefik/paerser v0.2.2 // indirect
github.com/unrolled/render v1.7.0 // indirect
github.com/vulcand/predicate v1.3.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
Expand Down Expand Up @@ -138,6 +140,8 @@ github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -160,6 +164,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
Expand Down
146 changes: 125 additions & 21 deletions internal/controllers/ingressroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,56 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

// IngressRouteReconciler reconciles an IngressRoute object.
// ingressAdapter abstracts the type-specific operations for different Traefik ingress resources.
type ingressAdapter interface {
// newResource returns a new empty instance of the ingress resource.
newResource() client.Object
// extractInfo extracts IngressInfo from a fetched ingress resource.
extractInfo(client.Object) (integrations.IngressInfo, error)
// setupWithManager configures the controller watches for this resource type.
setupWithManager(mgr ctrl.Manager, r *IngressRouteReconciler) error
}

// IngressRouteReconciler reconciles Traefik ingress route objects, including both IngressRoute
// and IngressRouteTCP resources.
type IngressRouteReconciler struct {
client.Client
logger *slog.Logger
selector switchboard.Selector
integrations []integrations.Integration
adapter ingressAdapter
}

// NewIngressRouteReconciler creates a new IngressRouteReconciler.
func NewIngressRouteReconciler(
client client.Client, logger *slog.Logger, config configv1.Config,
func newReconciler(
client client.Client, logger *slog.Logger, config configv1.Config, adapter ingressAdapter,
) (IngressRouteReconciler, error) {
integrations, err := integrationsFromConfig(config, client)
itgs, err := integrationsFromConfig(config, client)
if err != nil {
return IngressRouteReconciler{}, fmt.Errorf("failed to initialize integrations: %s", err)
}
return IngressRouteReconciler{
Client: client,
logger: logger,
selector: switchboard.NewSelector(config.Selector.IngressClass),
integrations: integrations,
integrations: itgs,
adapter: adapter,
}, nil
}

// NewIngressRouteReconciler creates a new reconciler for IngressRoute resources.
func NewIngressRouteReconciler(
client client.Client, logger *slog.Logger, config configv1.Config,
) (IngressRouteReconciler, error) {
return newReconciler(client, logger, config, ingressRouteAdapter{})
}

// NewIngressRouteTCPReconciler creates a new reconciler for IngressRouteTCP resources.
func NewIngressRouteTCPReconciler(
client client.Client, logger *slog.Logger, config configv1.Config,
) (IngressRouteReconciler, error) {
return newReconciler(client, logger, config, ingressRouteTCPAdapter{})
}

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *IngressRouteReconciler) Reconcile(
Expand All @@ -47,46 +73,38 @@ func (r *IngressRouteReconciler) Reconcile(
logger := r.logger.With("name", req.String())

// First, we retrieve the full resource
var ingressRoute traefik.IngressRoute
resource := r.adapter.newResource()

if err := r.Get(ctx, req.NamespacedName, &ingressRoute); err != nil {
if err := r.Get(ctx, req.NamespacedName, resource); err != nil {
if !apierrs.IsNotFound(err) {
logger.Error("unable to query for ingress route", "error", err)
}
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Then, we check if the resource should be processed
if !r.selector.Matches(ingressRoute.Annotations) {
if !r.selector.Matches(resource.GetAnnotations()) {
logger.Debug("ignoring ingress route")
return ctrl.Result{}, nil
}
logger.Debug("reconciling ingress route")

// Now, we have to ensure that all the dependent resources exist by calling all integrations.
// For this, we first have to extract information about the ingress.
collection, err := switchboard.NewHostCollection().
WithTLSHostsIfAvailable(ingressRoute.Spec.TLS).
WithRouteHostsIfRequired(ingressRoute.Spec.Routes)
info, err := r.adapter.extractInfo(resource)
if err != nil {
logger.Error("failed to parse hosts from ingress route", "error", err)
return ctrl.Result{}, err
}
info := integrations.IngressInfo{
Hosts: collection.Hosts(),
TLSSecretName: ext.AndThen(ingressRoute.Spec.TLS, func(tls traefik.TLS) string {
return tls.SecretName
}),
}

// Then, we can run the integrations
for _, itg := range r.integrations {
if !r.selector.MatchesIntegration(ingressRoute.Annotations, itg.Name()) {
if !r.selector.MatchesIntegration(resource.GetAnnotations(), itg.Name()) {
// If integration is ignored, skip it
logger.Debug("ignoring integration", "integration", itg.Name())
continue
}
if err := itg.UpdateResource(ctx, &ingressRoute, info); err != nil {
if err := itg.UpdateResource(ctx, resource, info); err != nil {
logger.Error("failed to upsert resource",
"integration", itg.Name(), "error", err,
)
Expand All @@ -101,7 +119,93 @@ func (r *IngressRouteReconciler) Reconcile(

// SetupWithManager sets up the controller with the Manager.
func (r *IngressRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
return r.adapter.setupWithManager(mgr, r)
}

//-------------------------------------------------------------------------------------------------
// INGRESS ROUTE ADAPTER
//-------------------------------------------------------------------------------------------------

type ingressRouteAdapter struct{}

func (a ingressRouteAdapter) newResource() client.Object {
return &traefik.IngressRoute{}
}

func (a ingressRouteAdapter) extractInfo(obj client.Object) (integrations.IngressInfo, error) {
ir, ok := obj.(*traefik.IngressRoute)
if !ok {
return integrations.IngressInfo{}, fmt.Errorf("unexpected resource type %T", obj)
}
collection, err := switchboard.NewHostCollection().
WithTLSHostsIfAvailable(ir.Spec.TLS).
WithRouteHostsIfRequired(ir.Spec.Routes)
if err != nil {
return integrations.IngressInfo{}, err
}
return integrations.IngressInfo{
Hosts: collection.Hosts(),
TLSSecretName: ext.AndThen(ir.Spec.TLS, func(tls traefik.TLS) string {
return tls.SecretName
}),
}, nil
}

func (a ingressRouteAdapter) setupWithManager(
mgr ctrl.Manager, r *IngressRouteReconciler,
) error {
var list traefik.IngressRouteList
builder := ctrl.NewControllerManagedBy(mgr).For(&traefik.IngressRoute{})
builder = builderWithIntegrations(builder, r.integrations, r, r.logger)
builder = builderWithIntegrations(builder, r.integrations, r, r.logger, &list,
func(list *traefik.IngressRouteList) []client.Object {
return ext.Map(list.Items, func(v traefik.IngressRoute) client.Object {
return &v
})
},
)
return builder.Complete(r)
}

//-------------------------------------------------------------------------------------------------
// INGRESS ROUTE TCP ADAPTER
//-------------------------------------------------------------------------------------------------

type ingressRouteTCPAdapter struct{}

func (a ingressRouteTCPAdapter) newResource() client.Object {
return &traefik.IngressRouteTCP{}
}

func (a ingressRouteTCPAdapter) extractInfo(obj client.Object) (integrations.IngressInfo, error) {
ir, ok := obj.(*traefik.IngressRouteTCP)
if !ok {
return integrations.IngressInfo{}, fmt.Errorf("unexpected resource type %T", obj)
}
collection, err := switchboard.NewHostCollection().
WithTLSTCPHostsIfAvailable(ir.Spec.TLS).
WithRouteTCPHostsIfRequired(ir.Spec.Routes)
if err != nil {
return integrations.IngressInfo{}, err
}
return integrations.IngressInfo{
Hosts: collection.Hosts(),
TLSSecretName: ext.AndThen(ir.Spec.TLS, func(tls traefik.TLSTCP) string {
return tls.SecretName
}),
}, nil
}

func (a ingressRouteTCPAdapter) setupWithManager(
mgr ctrl.Manager, r *IngressRouteReconciler,
) error {
var list traefik.IngressRouteTCPList
builder := ctrl.NewControllerManagedBy(mgr).For(&traefik.IngressRouteTCP{})
builder = builderWithIntegrations(builder, r.integrations, r, r.logger, &list,
func(list *traefik.IngressRouteTCPList) []client.Object {
return ext.Map(list.Items, func(v traefik.IngressRouteTCP) client.Object {
return &v
})
},
)
return builder.Complete(r)
}
Loading