From c822238fbe4495ce5b9e6068900f268000420df0 Mon Sep 17 00:00:00 2001 From: Daniel Raap Date: Mon, 1 Jun 2026 18:25:43 +0200 Subject: [PATCH 1/5] Add `httpRouteOptions` to CRD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit == Prompt for Copilot with Claude Sonnet 4.6: This Kubernetes CustomResourceDefinition for a SolrCloud resource should be extended. Like the support for `Ingress` it should be possible to specify `HTTPRoute` settings of the Gateway API. The CRD should support a list of HTTPRoutes to optionally generate multiple kubernetes resources. A simple example of a HTTPRoute resource looks like the following: ``` apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: example-route spec: parentRefs: - name: example-gateway hostnames: - "example.com" rules: - backendRefs: - name: example-svc port: 80 ``` Some information about the gateway API about routing rules: - The path-based routing rules of the Ingress resource map directly to the routing rules of the HTTPRoute. - In Gateway API, TLS termination is a property of the Gateway listener, and similarly to the Ingress, a TLS certificate and key are also stored in a Secret. - Because the listener is part of the Gateway resource, the cluster operator and application admin own TLS termination. - HTTPRoutes are owned by the application developer. - The Ingress default backend configures a backend that will respond to all unmatched HTTP requests related to that Ingress resource. Gateway API does not have a direct equivalent: it is necessary to define such a routing rule explicitly. We just want support for HTTPRoutes as application developer. The Gateway specification is out of scope for this change. Here is an example how to convert a Ingress to HTTPRoute: The Ingress below will be converted as an example: ``` apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress annotations: example-ingress-controller.example.org/tls-redirect: "True" spec: ingressClassName: prod tls: - hosts: - foo.example.com - bar.example.com secretName: example-com rules: - host: foo.example.com http: paths: - path: / pathType: Prefix backend: service: name: foo-app port: number: 80 - path: /orders pathType: Prefix backend: service: name: foo-orders-app port: number: 80 - host: bar.example.com http: paths: - path: / pathType: Prefix backend: service: name: bar-app port: number: 80 ``` The Ingress is split into two HTTPRoutes – one for foo.example.com and one for bar.example.com hostnames. ``` apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: foo spec: parentRefs: - name: example-gateway sectionName: https hostnames: - foo.example.com rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: foo-app port: 80 - matches: - path: type: PathPrefix value: /orders backendRefs: - name: foo-orders-app port: 80 ``` == Summary of Copilot changes Changes Made 1. `api/v1beta1/common_types.go` — Two new types added: • `HTTPRouteOptions`: Defines the options for a single HTTPRoute resource to be generated. Fields: • name (required) — the name of the Kubernetes HTTPRoute resource • annotations — metadata annotations • labels — metadata labels • parentRefs — list of parent Gateway references to attach the route to • hostnames — hostnames this route should match • `HTTPRouteParentReference`: Defines a reference to a parent Gateway resource. Fields: • name (required) — Gateway name • group — API group (defaults to gateway.networking.k8s.io) • kind — resource kind (defaults to Gateway) • namespace — Gateway namespace • sectionName — specific listener name on the Gateway • port — target port on the Gateway 2. `api/v1beta1/solrcloud_types.go` — Added to CustomSolrKubeOptions: ``` HTTPRouteOptions []HTTPRouteOptions `json:"httpRouteOptions,omitempty"` ``` A separate HTTPRoute Kubernetes resource will be generated for each entry in this list. 3. `api/v1beta1/zz_generated.deepcopy.go` — Added proper DeepCopyInto/DeepCopy implementations for both HTTPRouteOptions and HTTPRouteParentReference, and extended CustomSolrKubeOptions.DeepCopyInto to handle the new slice field. 4. `config/crd/bases/solr.apache.org_solrclouds.yaml` — Added the full OpenAPI v3 schema for httpRouteOptions (as an array) within the customSolrKubeOptions properties section. --- api/v1beta1/common_types.go | 53 +++++++++++ api/v1beta1/solrcloud_types.go | 5 ++ api/v1beta1/zz_generated.deepcopy.go | 88 +++++++++++++++++++ .../crd/bases/solr.apache.org_solrclouds.yaml | 70 +++++++++++++++ 4 files changed, 216 insertions(+) diff --git a/api/v1beta1/common_types.go b/api/v1beta1/common_types.go index 22002243..f4b0f292 100644 --- a/api/v1beta1/common_types.go +++ b/api/v1beta1/common_types.go @@ -194,6 +194,59 @@ type IngressOptions struct { IngressClassName *string `json:"ingressClassName,omitempty"` } +// HTTPRouteOptions defines the options for generating an HTTPRoute resource of the Gateway API. +type HTTPRouteOptions struct { + // Name is the name of the HTTPRoute Kubernetes resource to be created. + Name string `json:"name"` + + // Annotations to be added for the HTTPRoute. + // +optional + Annotations map[string]string `json:"annotations,omitempty"` + + // Labels to be added for the HTTPRoute. + // +optional + Labels map[string]string `json:"labels,omitempty"` + + // ParentRefs references the resources (usually Gateways) that this HTTPRoute wants to be attached to. + // +optional + ParentRefs []HTTPRouteParentReference `json:"parentRefs,omitempty"` + + // Hostnames defines a set of hostnames that should match this HTTPRoute. + // At least one hostname should be provided. + // +optional + Hostnames []string `json:"hostnames,omitempty"` +} + +// HTTPRouteParentReference defines a reference to a parent resource to which an HTTPRoute should be attached. +// This is typically a Gateway resource. +type HTTPRouteParentReference struct { + // Group is the group of the referent. + // Defaults to "gateway.networking.k8s.io". + // +optional + Group *string `json:"group,omitempty"` + + // Kind is the Kubernetes kind of the referent. + // Defaults to "Gateway". + // +optional + Kind *string `json:"kind,omitempty"` + + // Namespace is the namespace of the referent. + // Defaults to the namespace of the HTTPRoute. + // +optional + Namespace *string `json:"namespace,omitempty"` + + // Name is the name of the referent. + Name string `json:"name"` + + // SectionName is the name of a section within the target resource (e.g., a listener name on a Gateway). + // +optional + SectionName *string `json:"sectionName,omitempty"` + + // Port is the network port this Route targets on the referenced parent resource. + // +optional + Port *int32 `json:"port,omitempty"` +} + // ConfigMapOptions defines custom options for configMaps type ConfigMapOptions struct { // Annotations to be added for the ConfigMap. diff --git a/api/v1beta1/solrcloud_types.go b/api/v1beta1/solrcloud_types.go index 18930268..a7ba8af2 100644 --- a/api/v1beta1/solrcloud_types.go +++ b/api/v1beta1/solrcloud_types.go @@ -237,6 +237,11 @@ type CustomSolrKubeOptions struct { // IngressOptions defines the custom options for the solrCloud Ingress. // +optional IngressOptions *IngressOptions `json:"ingressOptions,omitempty"` + + // HTTPRouteOptions defines the custom options for HTTPRoute resources of the Gateway API to be generated for the solrCloud. + // A separate HTTPRoute resource will be generated for each entry in this list. + // +optional + HTTPRouteOptions []HTTPRouteOptions `json:"httpRouteOptions,omitempty"` } type SolrDataStorageOptions struct { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index d48f048e..6f130c3a 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -209,6 +209,13 @@ func (in *CustomSolrKubeOptions) DeepCopyInto(out *CustomSolrKubeOptions) { *out = new(IngressOptions) (*in).DeepCopyInto(*out) } + if in.HTTPRouteOptions != nil { + in, out := &in.HTTPRouteOptions, &out.HTTPRouteOptions + *out = make([]HTTPRouteOptions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomSolrKubeOptions. @@ -361,6 +368,87 @@ func (in *IngressOptions) DeepCopy() *IngressOptions { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPRouteOptions) DeepCopyInto(out *HTTPRouteOptions) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.ParentRefs != nil { + in, out := &in.ParentRefs, &out.ParentRefs + *out = make([]HTTPRouteParentReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Hostnames != nil { + in, out := &in.Hostnames, &out.Hostnames + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteOptions. +func (in *HTTPRouteOptions) DeepCopy() *HTTPRouteOptions { + if in == nil { + return nil + } + out := new(HTTPRouteOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPRouteParentReference) DeepCopyInto(out *HTTPRouteParentReference) { + *out = *in + if in.Group != nil { + in, out := &in.Group, &out.Group + *out = new(string) + **out = **in + } + if in.Kind != nil { + in, out := &in.Kind, &out.Kind + *out = new(string) + **out = **in + } + if in.Namespace != nil { + in, out := &in.Namespace, &out.Namespace + *out = new(string) + **out = **in + } + if in.SectionName != nil { + in, out := &in.SectionName, &out.SectionName + *out = new(string) + **out = **in + } + if in.Port != nil { + in, out := &in.Port, &out.Port + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteParentReference. +func (in *HTTPRouteParentReference) DeepCopy() *HTTPRouteParentReference { + if in == nil { + return nil + } + out := new(HTTPRouteParentReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ManagedUpdateOptions) DeepCopyInto(out *ManagedUpdateOptions) { *out = *in diff --git a/config/crd/bases/solr.apache.org_solrclouds.yaml b/config/crd/bases/solr.apache.org_solrclouds.yaml index 20a9a33c..df705662 100644 --- a/config/crd/bases/solr.apache.org_solrclouds.yaml +++ b/config/crd/bases/solr.apache.org_solrclouds.yaml @@ -2192,6 +2192,76 @@ spec: description: Labels to be added for the Ingress. type: object type: object + httpRouteOptions: + description: HTTPRouteOptions defines a list of custom options + for HTTPRoute resources of the Gateway API to be generated for + the solrCloud. A separate HTTPRoute resource will be generated + for each entry in this list. + items: + description: HTTPRouteOptions defines the options for generating + an HTTPRoute resource of the Gateway API. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to be added for the HTTPRoute. + type: object + hostnames: + description: Hostnames defines a set of hostnames that should + match this HTTPRoute. At least one hostname should be provided. + items: + type: string + type: array + labels: + additionalProperties: + type: string + description: Labels to be added for the HTTPRoute. + type: object + name: + description: Name is the name of the HTTPRoute Kubernetes + resource to be created. + type: string + parentRefs: + description: ParentRefs references the resources (usually + Gateways) that this HTTPRoute wants to be attached to. + items: + description: HTTPRouteParentReference defines a reference + to a parent resource to which an HTTPRoute should be + attached. This is typically a Gateway resource. + properties: + group: + description: Group is the group of the referent. Defaults + to gateway.networking.k8s.io. + type: string + kind: + description: Kind is the Kubernetes kind of the referent. + Defaults to Gateway. + type: string + name: + description: Name is the name of the referent. + type: string + namespace: + description: Namespace is the namespace of the referent. + Defaults to the namespace of the HTTPRoute. + type: string + port: + description: Port is the network port this Route targets + on the referenced parent resource. + format: int32 + type: integer + sectionName: + description: SectionName is the name of a section + within the target resource (e.g., a listener name + on a Gateway). + type: string + required: + - name + type: object + type: array + required: + - name + type: object + type: array nodeServiceOptions: description: |- NodeServiceOptions defines the custom options for the individual solrCloud Node services, if they are created. From c56cdfcc1a647ce599b8842133e345f85e925e32 Mon Sep 17 00:00:00 2001 From: Daniel Raap Date: Mon, 1 Jun 2026 18:55:33 +0200 Subject: [PATCH 2/5] implement controller code to generate HTTPRoute resources as definined in SolrCloud --- controllers/solrcloud_controller.go | 51 ++++++++++ controllers/util/common.go | 48 ++++++++- controllers/util/solr_httproute_util.go | 126 ++++++++++++++++++++++++ go.mod | 4 +- main.go | 3 + 5 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 controllers/util/solr_httproute_util.go diff --git a/controllers/solrcloud_controller.go b/controllers/solrcloud_controller.go index b18dbd14..54e7d22c 100644 --- a/controllers/solrcloud_controller.go +++ b/controllers/solrcloud_controller.go @@ -48,6 +48,7 @@ import ( "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" ) // SolrCloudReconciler reconciles a SolrCloud object @@ -70,6 +71,8 @@ func UseZkCRD(useCRD bool) { //+kubebuilder:rbac:groups=apps,resources=statefulsets/status,verbs=get //+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get +//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes/status,verbs=get //+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups="",resources=configmaps/status,verbs=get //+kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list;watch;delete @@ -436,6 +439,53 @@ func (r *SolrCloudReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } } + // Reconcile HTTPRoutes defined in HTTPRouteOptions + desiredHTTPRoutes := util.GenerateHTTPRoutes(instance) + desiredHTTPRouteNames := make(map[string]struct{}, len(desiredHTTPRoutes)) + for _, httpRoute := range desiredHTTPRoutes { + desiredHTTPRouteNames[httpRoute.Name] = struct{}{} + httpRouteLogger := logger.WithValues("httpRoute", httpRoute.Name) + foundHTTPRoute := &gatewayv1.HTTPRoute{} + err = r.Get(ctx, types.NamespacedName{Name: httpRoute.Name, Namespace: httpRoute.Namespace}, foundHTTPRoute) + if err != nil && errors.IsNotFound(err) { + httpRouteLogger.Info("Creating HTTPRoute") + if err = controllerutil.SetControllerReference(instance, httpRoute, r.Scheme); err == nil { + err = r.Create(ctx, httpRoute) + } + } else if err == nil { + var needsUpdate bool + needsUpdate, err = util.OvertakeControllerRef(instance, foundHTTPRoute, r.Scheme) + needsUpdate = util.CopyHTTPRouteFields(httpRoute, foundHTTPRoute, httpRouteLogger) || needsUpdate + if needsUpdate && err == nil { + httpRouteLogger.Info("Updating HTTPRoute") + err = r.Update(ctx, foundHTTPRoute) + } + } + if err != nil { + return requeueOrNot, err + } + } + + // Delete any HTTPRoutes that are owned by this SolrCloud but are no longer desired + existingHTTPRoutes := &gatewayv1.HTTPRouteList{} + if listErr := r.List(ctx, existingHTTPRoutes, + client.InNamespace(instance.GetNamespace()), + client.MatchingLabels(instance.SharedLabelsWith(map[string]string{})), + ); listErr == nil { + for i := range existingHTTPRoutes.Items { + existing := &existingHTTPRoutes.Items[i] + if metav1.IsControlledBy(existing, instance) { + if _, stillDesired := desiredHTTPRouteNames[existing.Name]; !stillDesired { + deleteLogger := logger.WithValues("httpRoute", existing.Name) + deleteLogger.Info("Deleting orphaned HTTPRoute") + if deleteErr := r.Delete(ctx, existing); deleteErr != nil && !errors.IsNotFound(deleteErr) { + return requeueOrNot, deleteErr + } + } + } + } + } + // ********************************************************* // The operations after this require a statefulSet to exist, // including updating the solrCloud status @@ -1191,6 +1241,7 @@ func (r *SolrCloudReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.Service{}). Owns(&corev1.Secret{}). /* for authentication */ Owns(&netv1.Ingress{}). + Owns(&gatewayv1.HTTPRoute{}). Owns(&policyv1.PodDisruptionBudget{}) var err error diff --git a/controllers/util/common.go b/controllers/util/common.go index 0f7fdaa9..34fdd1ba 100644 --- a/controllers/util/common.go +++ b/controllers/util/common.go @@ -33,6 +33,7 @@ import ( "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" ) var ( @@ -310,6 +311,51 @@ func CopyIngressFields(from, to *netv1.Ingress, logger logr.Logger) bool { return requireUpdate } +// CopyHTTPRouteFields copies the owned fields from one HTTPRoute to another. +// Returns true if any field was changed. +func CopyHTTPRouteFields(from, to *gatewayv1.HTTPRoute, logger logr.Logger) bool { + logger = logger.WithValues("kind", "httpRoute") + requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger) + + if !DeepEqualWithNils(to.Spec.ParentRefs, from.Spec.ParentRefs) { + requireUpdate = true + logger.Info("Update required because field changed", "field", "Spec.ParentRefs", "from", to.Spec.ParentRefs, "to", from.Spec.ParentRefs) + to.Spec.ParentRefs = from.Spec.ParentRefs + } + + if !DeepEqualWithNils(to.Spec.Hostnames, from.Spec.Hostnames) { + requireUpdate = true + logger.Info("Update required because field changed", "field", "Spec.Hostnames", "from", to.Spec.Hostnames, "to", from.Spec.Hostnames) + to.Spec.Hostnames = from.Spec.Hostnames + } + + if len(to.Spec.Rules) != len(from.Spec.Rules) { + requireUpdate = true + logger.Info("Update required because field changed", "field", "Spec.Rules", "from", to.Spec.Rules, "to", from.Spec.Rules) + to.Spec.Rules = from.Spec.Rules + } else { + for i := range from.Spec.Rules { + fromRule := &from.Spec.Rules[i] + toRule := &to.Spec.Rules[i] + ruleBase := "Spec.Rules[" + strconv.Itoa(i) + "]." + + if !DeepEqualWithNils(toRule.Matches, fromRule.Matches) { + requireUpdate = true + logger.Info("Update required because field changed", "field", ruleBase+"Matches", "from", toRule.Matches, "to", fromRule.Matches) + toRule.Matches = fromRule.Matches + } + + if !DeepEqualWithNils(toRule.BackendRefs, fromRule.BackendRefs) { + requireUpdate = true + logger.Info("Update required because field changed", "field", ruleBase+"BackendRefs", "from", toRule.BackendRefs, "to", fromRule.BackendRefs) + toRule.BackendRefs = fromRule.BackendRefs + } + } + } + + return requireUpdate +} + // CopyStatefulSetFields copies the owned fields from one StatefulSet to another // Returns true if the fields copied from don't match to. func CopyStatefulSetFields(from, to *appsv1.StatefulSet, logger logr.Logger) bool { @@ -752,4 +798,4 @@ func OvertakeControllerRef(owner metav1.Object, controlled metav1.Object, scheme needsUpdate = true } return needsUpdate, err -} +} \ No newline at end of file diff --git a/controllers/util/solr_httproute_util.go b/controllers/util/solr_httproute_util.go new file mode 100644 index 00000000..6a328bab --- /dev/null +++ b/controllers/util/solr_httproute_util.go @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +import ( + solr "github.com/apache/solr-operator/api/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +// GenerateHTTPRoutes returns a list of HTTPRoute pointers generated for the SolrCloud, +// one for each entry in Spec.CustomSolrKubeOptions.HTTPRouteOptions. +func GenerateHTTPRoutes(solrCloud *solr.SolrCloud) []*gatewayv1.HTTPRoute { + routes := make([]*gatewayv1.HTTPRoute, 0, len(solrCloud.Spec.CustomSolrKubeOptions.HTTPRouteOptions)) + for i := range solrCloud.Spec.CustomSolrKubeOptions.HTTPRouteOptions { + routes = append(routes, GenerateHTTPRoute(solrCloud, &solrCloud.Spec.CustomSolrKubeOptions.HTTPRouteOptions[i])) + } + return routes +} + +// GenerateHTTPRoute returns an HTTPRoute pointer generated for the SolrCloud based on the given HTTPRouteOptions. +// The routing rule routes all traffic (PathPrefix "/") to the Solr common service. +func GenerateHTTPRoute(solrCloud *solr.SolrCloud, opts *solr.HTTPRouteOptions) *gatewayv1.HTTPRoute { + labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels()) + labels = MergeLabelsOrAnnotations(labels, opts.Labels) + + var annotations map[string]string + if len(opts.Annotations) > 0 { + annotations = MergeLabelsOrAnnotations(annotations, opts.Annotations) + } + + // Build Gateway API ParentReferences from the options + parentRefs := make([]gatewayv1.ParentReference, 0, len(opts.ParentRefs)) + for _, pr := range opts.ParentRefs { + ref := gatewayv1.ParentReference{ + Name: gatewayv1.ObjectName(pr.Name), + } + if pr.Group != nil { + g := gatewayv1.Group(*pr.Group) + ref.Group = &g + } + if pr.Kind != nil { + k := gatewayv1.Kind(*pr.Kind) + ref.Kind = &k + } + if pr.Namespace != nil { + ns := gatewayv1.Namespace(*pr.Namespace) + ref.Namespace = &ns + } + if pr.SectionName != nil { + sn := gatewayv1.SectionName(*pr.SectionName) + ref.SectionName = &sn + } + if pr.Port != nil { + pn := gatewayv1.PortNumber(*pr.Port) + ref.Port = &pn + } + parentRefs = append(parentRefs, ref) + } + + // Build hostnames + hostnames := make([]gatewayv1.Hostname, 0, len(opts.Hostnames)) + for _, h := range opts.Hostnames { + hostnames = append(hostnames, gatewayv1.Hostname(h)) + } + + // Build routing rules: route all traffic (PathPrefix "/") to the Solr common service. + // This mirrors the common Ingress rule that routes to the common service. + pathPrefix := "/" + pathPrefixType := gatewayv1.PathMatchPathPrefix + commonServicePort := gatewayv1.PortNumber(solrCloud.Spec.SolrAddressability.CommonServicePort) + + rules := []gatewayv1.HTTPRouteRule{ + { + Matches: []gatewayv1.HTTPRouteMatch{ + { + Path: &gatewayv1.HTTPPathMatch{ + Type: &pathPrefixType, + Value: &pathPrefix, + }, + }, + }, + BackendRefs: []gatewayv1.HTTPBackendRef{ + { + BackendRef: gatewayv1.BackendRef{ + BackendObjectReference: gatewayv1.BackendObjectReference{ + Name: gatewayv1.ObjectName(solrCloud.CommonServiceName()), + Port: &commonServicePort, + }, + }, + }, + }, + }, + } + + return &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: opts.Name, + Namespace: solrCloud.GetNamespace(), + Labels: labels, + Annotations: annotations, + }, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: parentRefs, + }, + Hostnames: hostnames, + Rules: rules, + }, + } +} diff --git a/go.mod b/go.mod index 9e1daf15..ca4372ae 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( k8s.io/client-go v0.31.3 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 sigs.k8s.io/controller-runtime v0.19.4 + sigs.k8s.io/gateway-api v1.1.0 ) require ( @@ -159,10 +160,9 @@ require ( k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f // indirect k8s.io/kubectl v0.31.3 // indirect oras.land/oras-go v1.2.5 // indirect - sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.17.2 // indirect sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect -) +) \ No newline at end of file diff --git a/main.go b/main.go index c4aee805..02503bf5 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,7 @@ import ( solrv1beta1 "github.com/apache/solr-operator/api/v1beta1" "github.com/apache/solr-operator/controllers" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" //+kubebuilder:scaffold:imports ) @@ -86,6 +87,8 @@ func init() { utilruntime.Must(solrv1beta1.AddToScheme(scheme)) utilruntime.Must(zkApi.AddToScheme(scheme)) + + utilruntime.Must(gatewayv1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme flag.BoolVar(&useZookeeperCRD, "zk-operator", true, "The operator will not use the zk operator & crd when this flag is set to false.") From 956579336478b34cd8c702047132464120788780 Mon Sep 17 00:00:00 2001 From: Daniel Raap Date: Mon, 1 Jun 2026 19:20:49 +0200 Subject: [PATCH 3/5] run action 'Build & Check' on each commit of my feature branch --- .github/workflows/check.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 47ddda4b..341e55a2 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -16,6 +16,9 @@ name: Build & Check (Lint & Unit Test) on: + push: + branches: + - 'issue_829_gateway_support' pull_request: branches: - 'main' From 8e34fad132d094777d3e454da8ed911c1e313264 Mon Sep 17 00:00:00 2001 From: Daniel Raap Date: Mon, 1 Jun 2026 19:25:13 +0200 Subject: [PATCH 4/5] fix lint error, add TODO for revert of workflow --- .github/workflows/check.yaml | 1 + controllers/util/common.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 341e55a2..2db04cd4 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -16,6 +16,7 @@ name: Build & Check (Lint & Unit Test) on: + # TODO remove before merge in main! push: branches: - 'issue_829_gateway_support' diff --git a/controllers/util/common.go b/controllers/util/common.go index 34fdd1ba..0071986d 100644 --- a/controllers/util/common.go +++ b/controllers/util/common.go @@ -798,4 +798,4 @@ func OvertakeControllerRef(owner metav1.Object, controlled metav1.Object, scheme needsUpdate = true } return needsUpdate, err -} \ No newline at end of file +} From e0395af8cfba8440ee7c2b9a9ce0ab66f8ec810c Mon Sep 17 00:00:00 2001 From: Daniel Raap Date: Mon, 1 Jun 2026 19:36:11 +0200 Subject: [PATCH 5/5] add newline at end of file --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ca4372ae..93456e67 100644 --- a/go.mod +++ b/go.mod @@ -165,4 +165,4 @@ require ( sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect -) \ No newline at end of file +)