diff --git a/README.md b/README.md index 993f84a..d28e42a 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Support for missing resources is planned but not yet implemented. - [x] [BackendTLSPolicy](https://gateway-api.sigs.k8s.io/api-types/backendtlspolicy/) - [x] [HTTPRoute](https://gateway-api.sigs.k8s.io/api-types/httproute/) - [ ] [GRPCRoute](https://gateway-api.sigs.k8s.io/api-types/grpcroute/) -- [ ] [TLSRoute](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tlsroute) +- [x] [TLSRoute](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tlsroute) - [x] [TCPRoute](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tcproute-and-udproute) - [x] [UDPRoute](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tcproute-and-udproute) diff --git a/internal/caddy/caddy.go b/internal/caddy/caddy.go index 91bf915..ad087ef 100644 --- a/internal/caddy/caddy.go +++ b/internal/caddy/caddy.go @@ -123,13 +123,13 @@ func (i *Input) handleListener(l gatewayv1.Listener) error { case gatewayv1.HTTPProtocolType: return i.handleHTTPListener(l) case gatewayv1.HTTPSProtocolType: + // If TLS mode is not Terminate, then ignore the listener. We cannot do HTTP routing while + // doing TLS passthrough as we need to decrypt the request in order to route it. + if l.TLS != nil && l.TLS.Mode != nil && *l.TLS.Mode != gatewayv1.TLSModeTerminate { + return nil + } return i.handleHTTPListener(l) case gatewayv1.TLSProtocolType: - // If TLS mode is set to Terminate, treat it as an HTTP server. - if l.TLS == nil || l.TLS.Mode == nil || *l.TLS.Mode == gatewayv1.TLSModeTerminate { - return i.handleHTTPListener(l) - } - // Otherwise we need TLS passthrough, which is more complicated. return i.handleLayer4Listener(l) case gatewayv1.TCPProtocolType: return i.handleLayer4Listener(l) @@ -194,7 +194,7 @@ func (i *Input) handleLayer4Listener(l gatewayv1.Listener) error { s, ok := i.layer4Servers[key] if !ok { s = &layer4.Server{ - Listen: []string{proto + "/" + ":" + strconv.Itoa(int(l.Port))}, + Listen: []string{proto + "/:" + strconv.Itoa(int(l.Port))}, } } @@ -204,9 +204,7 @@ func (i *Input) handleLayer4Listener(l gatewayv1.Listener) error { ) switch l.Protocol { case gatewayv1.TLSProtocolType: - // TODO: implement - // This TLS protocol is for passthrough, not terminate. - break + server, err = i.getTLSServer(s, l) case gatewayv1.TCPProtocolType: server, err = i.getTCPServer(s, l) case gatewayv1.UDPProtocolType: diff --git a/internal/caddy/tls_passthrough.go b/internal/caddy/tls_passthrough.go index 9d9949c..50dbb87 100644 --- a/internal/caddy/tls_passthrough.go +++ b/internal/caddy/tls_passthrough.go @@ -3,36 +3,109 @@ package caddy -//// getTLSServer . -//// TODO: document -//func (i *Input) getTLSServer(l gatewayv1.Listener) (*layer4.Server, error) { -// // TODO: protocol may be either TLS or HTTPS, we should configure the host -// // matcher accordingly. -// var hostname string -// if l.Hostname != nil { -// hostname = string(*l.Hostname) -// } -// -// tls := map[string]any{"sni": []string{hostname}} -// tlsJson, err := json.Marshal(tls) -// if err != nil { -// return nil, err -// } -// -// return &layer4.Server{ -// Listen: []string{":" + strconv.Itoa(int(l.Port))}, -// Routes: layer4.RouteList{ -// { -// MatcherSetsRaw: caddyhttp.RawMatcherSets{ -// { -// // TODO: if no hostname was set can we just leave an empty matcher? -// "tls": tlsJson, -// }, -// }, -// HandlersRaw: []json.RawMessage{ -// json.RawMessage(`{"handler":"proxy","upstreams":[{"dial":""}]}`), -// }, -// }, -// }, -// }, nil -//} +import ( + "net" + "strconv" + + gateway "github.com/caddyserver/gateway/internal" + "github.com/caddyserver/gateway/internal/layer4" + "github.com/caddyserver/gateway/internal/layer4/l4proxy" + "github.com/caddyserver/gateway/internal/layer4/l4tls" + corev1 "k8s.io/api/core/v1" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +// getTLSServer . +// TODO: document +func (i *Input) getTLSServer(s *layer4.Server, l gatewayv1.Listener) (*layer4.Server, error) { + routes := []*layer4.Route{} + for _, tr := range i.TLSRoutes { + if !isRouteForListener(i.Gateway, l, tr.Namespace, tr.Status.RouteStatus) { + continue + } + + matchers := []layer4.Match{} + // Match hostnames if any are specified. + if len(tr.Spec.Hostnames) > 0 { + // TODO: validate hostnames against listener hostnames, including + // a prefix match for wildcards. + // + // See godoc for HTTPRoute.Spec.Hostnames for more details. + matcher := layer4.Match{ + TLS: &layer4.MatchTLS{ + SNI: make(layer4.MatchSNI, len(tr.Spec.Hostnames)), + }, + } + for i, h := range tr.Spec.Hostnames { + matcher.TLS.SNI[i] = string(h) + } + matchers = append(matchers, matcher) + } + + var handlers []layer4.Handler + if l.TLS == nil || l.TLS.Mode == nil || *l.TLS.Mode == gatewayv1.TLSModeTerminate { + // Add a TLS handler to terminate TLS. + handlers = []layer4.Handler{&l4tls.Handler{}} + } + + for _, rule := range tr.Spec.Rules { + // We only support a single backend ref as we don't support weights for layer4 proxy. + if len(rule.BackendRefs) != 1 { + continue + } + + bf := rule.BackendRefs[0] + bor := bf.BackendObjectReference + if !gateway.IsService(bor) { + continue + } + + // Safeguard against nil-pointer dereference. + if bor.Port == nil { + continue + } + + // Get the service. + // + // TODO: is there a more efficient way to do this? + // We currently list all services and forward them to the input, + // then iterate over them. + // + // Should we just use the Kubernetes client instead? + var service corev1.Service + for _, s := range i.Services { + if s.Namespace != gateway.NamespaceDerefOr(bor.Namespace, tr.Namespace) { + continue + } + if s.Name != string(bor.Name) { + continue + } + service = s + break + } + if service.Name == "" { + // Invalid service reference. + continue + } + + // Add a handler that proxies to the backend service. + handlers = append(handlers, &l4proxy.Handler{ + Upstreams: l4proxy.UpstreamPool{ + &l4proxy.Upstream{ + Dial: []string{net.JoinHostPort(service.Spec.ClusterIP, strconv.Itoa(int(*bor.Port)))}, + }, + }, + }) + } + + // Add the route. + routes = append(routes, &layer4.Route{ + MatcherSets: matchers, + Handlers: handlers, + }) + } + + // Update the routes on the server. + s.Routes = append(s.Routes, routes...) + return s, nil +} diff --git a/internal/controller/route_tls.go b/internal/controller/route_tls.go index c70fc7f..3aa3073 100644 --- a/internal/controller/route_tls.go +++ b/internal/controller/route_tls.go @@ -5,14 +5,29 @@ package controller import ( "context" + "fmt" + gateway "github.com/caddyserver/gateway/internal" + "github.com/caddyserver/gateway/internal/routechecks" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) // +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=tlsroutes,verbs=get;list;watch @@ -29,16 +44,242 @@ var _ reconcile.Reconciler = (*TLSRouteReconciler)(nil) // SetupWithManager sets up the controller with the Manager. func (r *TLSRouteReconciler) SetupWithManager(mgr ctrl.Manager) error { + ctx := context.Background() + + if err := mgr.GetFieldIndexer().IndexField(ctx, &gatewayv1alpha2.TLSRoute{}, backendServiceIndex, func(o client.Object) []string { + route, ok := o.(*gatewayv1alpha2.TLSRoute) + if !ok { + return nil + } + var backendServices []string + for _, rule := range route.Spec.Rules { + for _, backend := range rule.BackendRefs { + backendServiceName, err := gateway.GetBackendServiceName(backend.BackendObjectReference) + if err != nil { + mgr.GetLogger().WithValues( + "controller", "tls-route", + "resource", client.ObjectKeyFromObject(o), + ).Error(err, "Failed to get backend service name") + continue + } + + backendServices = append(backendServices, types.NamespacedName{ + Namespace: gateway.NamespaceDerefOr(backend.Namespace, route.Namespace), + Name: backendServiceName, + }.String()) + } + } + return backendServices + }); err != nil { + return err + } + + if err := mgr.GetFieldIndexer().IndexField(ctx, &gatewayv1alpha2.TLSRoute{}, gatewayIndex, func(o client.Object) []string { + hr := o.(*gatewayv1alpha2.TLSRoute) + var gateways []string + for _, parent := range hr.Spec.ParentRefs { + if !gateway.IsGateway(parent) { + continue + } + gateways = append(gateways, types.NamespacedName{ + Namespace: gateway.NamespaceDerefOr(parent.Namespace, hr.Namespace), + Name: string(parent.Name), + }.String()) + } + return gateways + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&gatewayv1alpha2.TLSRoute{}). + Watches(&corev1.Service{}, r.enqueueRequestForBackendService()). + Watches(&gatewayv1beta1.ReferenceGrant{}, r.enqueueRequestForReferenceGrant()). + Watches( + &gatewayv1.Gateway{}, + r.enqueueRequestForGateway(), + builder.WithPredicates(predicate.NewPredicateFuncs(r.hasMatchingController(ctx))), + ). Complete(r) } func (r *TLSRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.FromContext(ctx) - _ = log - // TODO: implement + original := &gatewayv1alpha2.TLSRoute{} + if err := r.Client.Get(ctx, req.NamespacedName, original); err != nil { + if apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + log.Error(err, "Unable to get TLSRoute") + return ctrl.Result{}, err + } + + // Check if the TLSRoute is being deleted. + if original.GetDeletionTimestamp() != nil { + return ctrl.Result{}, nil + } + + route := original.DeepCopy() + + grants := &gatewayv1beta1.ReferenceGrantList{} + if err := r.Client.List(ctx, grants); err != nil { + return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to retrieve reference grants: %w", err), original, route) + } + + // input for the validators + i := &routechecks.TLSRouteInput{ + Ctx: ctx, + Client: r.Client, + Grants: grants, + TLSRoute: route, + } + + // gateway validators + for _, parent := range route.Spec.ParentRefs { + // set acceptance to okay, this wil be overwritten in checks if needed + i.SetParentCondition(parent, metav1.Condition{ + Type: string(gatewayv1.RouteConditionAccepted), + Status: metav1.ConditionTrue, + Reason: string(gatewayv1.RouteReasonAccepted), + Message: "Accepted TLSRoute", + }) + + // set status to okay, this wil be overwritten in checks if needed + i.SetAllParentCondition(metav1.Condition{ + Type: string(gatewayv1.RouteConditionResolvedRefs), + Status: metav1.ConditionTrue, + Reason: string(gatewayv1.RouteReasonResolvedRefs), + Message: "Service reference is valid", + }) + // run the actual validators + for _, fn := range []routechecks.CheckGatewayFunc{ + routechecks.CheckGatewayAllowedForNamespace, + routechecks.CheckGatewayRouteKindAllowed, + routechecks.CheckGatewayMatchingPorts, + routechecks.CheckGatewayMatchingHostnames, + routechecks.CheckGatewayMatchingSection, + } { + continueCheck, err := fn(i, parent) + if err != nil { + return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to apply Gateway check: %w", err), original, route) + } + + if !continueCheck { + break + } + } + } + + for _, fn := range []routechecks.CheckRuleFunc{ + routechecks.CheckAgainstCrossNamespaceBackendReferences, + routechecks.CheckBackend, + routechecks.CheckBackendIsExistingService, + } { + continueCheck, err := fn(i) + if err != nil { + return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to apply Backend check: %w", err), original, route) + } + + if !continueCheck { + break + } + } + + if err := r.updateStatus(ctx, original, route); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update TLSRoute status: %w", err) + } + + log.Info("Reconciled TLSRoute") return ctrl.Result{}, nil } + +// enqueueRequestForBackendService . +// TODO: document +func (r *TLSRouteReconciler) enqueueRequestForBackendService() handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(r.enqueueFromIndex(backendServiceIndex)) +} + +// enqueueRequestForGateway . +// TODO: document +func (r *TLSRouteReconciler) enqueueRequestForGateway() handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(r.enqueueFromIndex(gatewayIndex)) +} + +// enqueueRequestForReferenceGrant . +// TODO: document +func (r *TLSRouteReconciler) enqueueRequestForReferenceGrant() handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(r.enqueueAll()) +} + +// enqueueFromIndex . +// TODO: document +func (r *TLSRouteReconciler) enqueueFromIndex(index string) handler.MapFunc { + return func(ctx context.Context, o client.Object) []reconcile.Request { + return r.enqueue(ctx, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(index, client.ObjectKeyFromObject(o).String()), + }) + } +} + +// enqueueAll . +// TODO +func (r *TLSRouteReconciler) enqueueAll() handler.MapFunc { + return func(ctx context.Context, _ client.Object) []reconcile.Request { + return r.enqueue(ctx) + } +} + +// enqueue . +// TODO +func (r *TLSRouteReconciler) enqueue(ctx context.Context, opts ...client.ListOption) []reconcile.Request { + log := log.FromContext(ctx) + + list := &gatewayv1alpha2.TLSRouteList{} + if err := r.Client.List(ctx, list, opts...); err != nil { + log.Error(err, "Failed to get TLSRoute") + return []reconcile.Request{} + } + + requests := make([]reconcile.Request, len(list.Items)) + for i, item := range list.Items { + route := types.NamespacedName{ + Namespace: item.GetNamespace(), + Name: item.GetName(), + } + requests[i] = reconcile.Request{ + NamespacedName: route, + } + log.Info("Enqueued TLSRoute for resource", "route", route) + } + return requests +} + +// hasMatchingController . +// TODO +func (r *TLSRouteReconciler) hasMatchingController(ctx context.Context) func(object client.Object) bool { + return hasMatchingController(ctx, r.Client) +} + +// updateStatus . +// TODO +func (r *TLSRouteReconciler) updateStatus(ctx context.Context, original, new *gatewayv1alpha2.TLSRoute) error { + oldStatus := original.Status.DeepCopy() + newStatus := new.Status.DeepCopy() + + opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") + if cmp.Equal(oldStatus, newStatus, opts) { + return nil + } + return r.Client.Status().Update(ctx, new) +} + +// handleReconcileErrorWithStatus . +// TODO +func (r *TLSRouteReconciler) handleReconcileErrorWithStatus(ctx context.Context, reconcileErr error, original, modified *gatewayv1alpha2.TLSRoute) (ctrl.Result, error) { + if err := r.updateStatus(ctx, original, modified); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update TLSRoute status while handling the reconcile error %w: %w", reconcileErr, err) + } + return ctrl.Result{}, reconcileErr +} diff --git a/internal/gateway.go b/internal/gateway.go index 72f1a6e..c7a8ea0 100644 --- a/internal/gateway.go +++ b/internal/gateway.go @@ -45,11 +45,6 @@ func IsService(be gatewayv1.BackendObjectReference) bool { return (be.Group == nil || *be.Group == corev1.GroupName) && (be.Kind == nil || *be.Kind == "Service") } -// // IsPolicyTargetService checks if the given PolicyTargetReference references a Service resource. -// func IsPolicyTargetService(be gatewayv1alpha2.PolicyTargetReference) bool { -// return be.Group == corev1.GroupName && be.Kind == "Service" -// } - // IsPolicyTargetService checks if the given PolicyTargetReference references a Service resource. func IsLocalPolicyTargetService(be gatewayv1alpha2.LocalPolicyTargetReference) bool { return be.Group == corev1.GroupName && be.Kind == "Service" diff --git a/internal/layer4/l4tls/tls.go b/internal/layer4/l4tls/tls.go new file mode 100644 index 0000000..a615038 --- /dev/null +++ b/internal/layer4/l4tls/tls.go @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright (c) 2024 Matthew Penner + +package l4tls + +import ( + "github.com/caddyserver/gateway/internal/caddyv2/caddytls" +) + +type HandlerName string + +func (HandlerName) MarshalJSON() ([]byte, error) { + return []byte(`"tls"`), nil +} + +// Handler is a connection handler that terminates TLS. +type Handler struct { + // Handler is the name of this handler for the JSON config. + // DO NOT USE this. This is a special value to represent this handler. + // It will be overwritten when we are marshalled. + Handler HandlerName `json:"handler"` + + ConnectionPolicies caddytls.ConnectionPolicies `json:"connection_policies,omitempty"` +} + +func (Handler) IAmAHandler() {} diff --git a/internal/layer4/matchers.go b/internal/layer4/matchers.go new file mode 100644 index 0000000..e5b59b3 --- /dev/null +++ b/internal/layer4/matchers.go @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright (c) 2024 Matthew Penner + +package layer4 + +// Match . +// TODO: document +type Match struct { + TLS *MatchTLS `json:"tls,omitempty"` +} + +func (m *Match) IsEmpty() bool { + if m == nil { + return true + } + if !m.TLS.IsEmpty() { + return false + } + return true +} + +// MatchTLS . +type MatchTLS struct { + SNI MatchSNI `json:"sni,omitempty"` +} + +func (m *MatchTLS) IsEmpty() bool { + if m == nil { + return true + } + if len(m.SNI) > 0 { + return false + } + return true +} + +// MatchSNI matches based on SNI (server name indication). +// ref; https://caddyserver.com/docs/modules/tls.handshake_match.sni +type MatchSNI []string diff --git a/internal/layer4/routes.go b/internal/layer4/routes.go index 9e83382..8b6d608 100644 --- a/internal/layer4/routes.go +++ b/internal/layer4/routes.go @@ -14,13 +14,11 @@ type Route struct { // All matchers within the same set must match, and at least one set // must match; in other words, matchers are AND'ed together within a // set, but multiple sets are OR'ed together. No matchers matches all. - MatcherSets []any `json:"match,omitempty"` - // MatcherSetsRaw []caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=layer4.matchers"` + MatcherSets []Match `json:"match,omitempty"` // Handlers define the behavior for handling the stream. They are // executed in sequential order if the route's matchers match. Handlers []Handler `json:"handle,omitempty"` - // HandlersRaw []json.RawMessage `json:"handle,omitempty" caddy:"namespace=layer4.handlers inline_key=handler"` } // RouteList is a list of connection routes that can create diff --git a/internal/routechecks/route_tls.go b/internal/routechecks/route_tls.go new file mode 100644 index 0000000..6fa1061 --- /dev/null +++ b/internal/routechecks/route_tls.go @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright (c) 2024 Matthew Penner + +package routechecks + +import ( + "context" + "fmt" + "reflect" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + gateway "github.com/caddyserver/gateway/internal" +) + +type TLSRouteInput struct { + Ctx context.Context + Client client.Client + Grants *gatewayv1beta1.ReferenceGrantList + TLSRoute *gatewayv1alpha2.TLSRoute + + gateways map[gatewayv1.ParentReference]*gatewayv1.Gateway +} + +func (h *TLSRouteInput) SetParentCondition(ref gatewayv1.ParentReference, condition metav1.Condition) { + // fill in the condition + condition.LastTransitionTime = metav1.NewTime(time.Now()) + condition.ObservedGeneration = h.TLSRoute.GetGeneration() + + h.mergeStatusConditions(ref, []metav1.Condition{ + condition, + }) +} + +func (h *TLSRouteInput) SetAllParentCondition(condition metav1.Condition) { + // fill in the condition + condition.LastTransitionTime = metav1.NewTime(time.Now()) + condition.ObservedGeneration = h.TLSRoute.GetGeneration() + + for _, parent := range h.TLSRoute.Spec.ParentRefs { + h.mergeStatusConditions(parent, []metav1.Condition{ + condition, + }) + } +} + +func (h *TLSRouteInput) mergeStatusConditions(parentRef gatewayv1.ParentReference, updates []metav1.Condition) { + index := -1 + for i, parent := range h.TLSRoute.Status.RouteStatus.Parents { + if reflect.DeepEqual(parent.ParentRef, parentRef) { + index = i + break + } + } + if index != -1 { + h.TLSRoute.Status.RouteStatus.Parents[index].Conditions = merge(h.TLSRoute.Status.RouteStatus.Parents[index].Conditions, updates...) + return + } + h.TLSRoute.Status.RouteStatus.Parents = append(h.TLSRoute.Status.RouteStatus.Parents, gatewayv1.RouteParentStatus{ + ParentRef: parentRef, + ControllerName: gateway.ControllerName, + Conditions: updates, + }) +} + +func (h *TLSRouteInput) GetGrants() []gatewayv1beta1.ReferenceGrant { + return h.Grants.Items +} + +func (h *TLSRouteInput) GetNamespace() string { + return h.TLSRoute.GetNamespace() +} + +func (h *TLSRouteInput) GetGVK() schema.GroupVersionKind { + return gatewayv1.SchemeGroupVersion.WithKind("TLSRoute") +} + +func (h *TLSRouteInput) GetRules() []GenericRule { + var rules []GenericRule + for _, rule := range h.TLSRoute.Spec.Rules { + rules = append(rules, &TLSRouteRule{rule}) + } + return rules +} + +func (h *TLSRouteInput) GetClient() client.Client { + return h.Client +} + +func (h *TLSRouteInput) GetContext() context.Context { + return h.Ctx +} + +func (h *TLSRouteInput) GetHostnames() []gatewayv1.Hostname { + return h.TLSRoute.Spec.Hostnames +} + +func (h *TLSRouteInput) GetGateway(parent gatewayv1.ParentReference) (*gatewayv1.Gateway, error) { + if h.gateways == nil { + h.gateways = make(map[gatewayv1.ParentReference]*gatewayv1.Gateway) + } + + if gw, exists := h.gateways[parent]; exists { + return gw, nil + } + + ns := gateway.NamespaceDerefOr(parent.Namespace, h.GetNamespace()) + gw := &gatewayv1.Gateway{} + + if err := h.Client.Get(h.Ctx, client.ObjectKey{Namespace: ns, Name: string(parent.Name)}, gw); err != nil { + if !apierrors.IsNotFound(err) { + // if it is not just a not found error, we should return the error as something is bad + return nil, fmt.Errorf("error while getting gateway: %w", err) + } + + // Gateway does not exist skip further checks + return nil, fmt.Errorf("gateway %q does not exist: %w", parent.Name, err) + } + + h.gateways[parent] = gw + + return gw, nil +} + +// TLSRouteRule is used to implement the GenericRule interface for TLSRoute +type TLSRouteRule struct { + Rule gatewayv1alpha2.TLSRouteRule +} + +func (t *TLSRouteRule) GetBackendRefs() []gatewayv1.BackendRef { + return t.Rule.BackendRefs +} diff --git a/main.go b/main.go index 37950a5..558f0c0 100644 --- a/main.go +++ b/main.go @@ -167,15 +167,15 @@ func main() { os.Exit(1) return } - //if err = (&controller.TLSRouteReconciler{ - // Client: client, - // Scheme: scheme, - // Recorder: recorder, - //}).SetupWithManager(mgr); err != nil { - // setupLog.Error(err, "unable to create controller", "controller", "TLSRoute") - // os.Exit(1) - // return - //} + if err = (&controller.TLSRouteReconciler{ + Client: client, + Scheme: scheme, + Recorder: recorder, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "TLSRoute") + os.Exit(1) + return + } if err = (&controller.UDPRouteReconciler{ Client: client, Scheme: scheme,