Skip to content

Commit

Permalink
Allow customize route via route.Override
Browse files Browse the repository at this point in the history
Adds an OverridSpec to the Route which allows to customize
metadata.Annotations, metadata.Labels and spec of a route. The
override values get merged into the object definition created by
the operator. This allows e.g. to add custom labels, configure
the route via annotations as in [1], or set TLS parameters.

[1] https://docs.openshift.com/container-platform/4.13/networking/routes/route-configuration.html#nw-route-specific-annotations_route-configuration

Jira: OSP-21715
Jira: OSP-26299
  • Loading branch information
stuggi committed Jul 11, 2023
1 parent 54bd95c commit 7450e90
Show file tree
Hide file tree
Showing 13 changed files with 647 additions and 16 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest

## Tool Versions
CONTROLLER_TOOLS_VERSION ?= v0.9.2
CONTROLLER_TOOLS_VERSION ?= v0.10.0

# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.24
ENVTEST_K8S_VERSION = 1.25


.PHONY: all
Expand Down
3 changes: 3 additions & 0 deletions modules/common/endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type Data struct {
Path string
// details for metallb service generation
MetalLB *MetalLBData
// possible overrides for Route
RouteOverride *route.OverrideSpec
}

// MetalLBData - information specific to creating the MetalLB service
Expand Down Expand Up @@ -184,6 +186,7 @@ func ExposeEndpoints(
}),
exportLabels,
timeout,
data.RouteOverride,
)

ctrlResult, err = route.CreateOrPatch(ctx, h)
Expand Down
6 changes: 5 additions & 1 deletion modules/common/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ require (
k8s.io/component-base v0.26.2 // indirect; indirect // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect; indirect // indirect
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect; indirect // indirect
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect; indirect // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
Expand All @@ -79,3 +79,7 @@ require (
github.com/kr/pretty v0.3.1 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
)

// mschuppert: map to latest commit from release-4.13 tag
// must consistent with common module
replace github.com/openshift/api => github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7
4 changes: 2 additions & 2 deletions modules/common/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs=
github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY=
github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7 h1:rncLxJBpFGqBztyxCMwNRnMjhhIDOWHJowi6q8G6koI=
github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7/go.mod h1:ctXNyWanKEjGj8sss1KjjHQ3ENKFm33FFnS5BKaIPh4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
45 changes: 42 additions & 3 deletions modules/common/route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package route

import (
"context"
"encoding/json"
"fmt"
"time"

Expand All @@ -29,6 +30,7 @@ import (
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/strategicpatch"
ctrl "sigs.k8s.io/controller-runtime"
)

Expand All @@ -37,10 +39,12 @@ func NewRoute(
route *routev1.Route,
labels map[string]string,
timeout time.Duration,
override *OverrideSpec,
) *Route {
return &Route{
route: route,
timeout: timeout,
route: route,
timeout: timeout,
override: override,
}
}

Expand Down Expand Up @@ -88,8 +92,43 @@ func (r *Route) CreateOrPatch(
},
}

// handle possible overrides of Labels, Annotations and Spec
if r.override != nil {
if r.override.EmbeddedLabelsAnnotations != nil {
if r.override.Labels != nil {
r.route.Labels = util.MergeStringMaps(r.override.Labels, r.route.Labels)
}
if r.override.Annotations != nil {
r.route.Annotations = util.MergeStringMaps(r.override.Annotations, r.route.Annotations)
}
}
if r.override.Spec != nil {
originalSpec, err := json.Marshal(r.route.Spec)
if err != nil {
return ctrl.Result{}, fmt.Errorf("error marshalling Route Spec: %w", err)
}

patch, err := json.Marshal(r.override.Spec)
if err != nil {
return ctrl.Result{}, fmt.Errorf("error marshalling Route Spec override: %w", err)
}

patchedJSON, err := strategicpatch.StrategicMergePatch(originalSpec, patch, routev1.RouteSpec{})
if err != nil {
return ctrl.Result{}, fmt.Errorf("error patching Route Spec: %w", err)
}

patchedSpec := routev1.RouteSpec{}
err = json.Unmarshal(patchedJSON, &patchedSpec)
if err != nil {
return ctrl.Result{}, fmt.Errorf("error unmarshalling patched Route Spec: %w", err)
}
r.route.Spec = patchedSpec
}
}

op, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), route, func() error {
route.Labels = util.MergeStringMaps(route.Labels, r.route.Labels)
route.Labels = r.route.Labels
route.Annotations = r.route.Annotations
route.Spec = r.route.Spec
if len(route.Spec.Host) == 0 && len(route.Status.Ingress) > 0 {
Expand Down
112 changes: 112 additions & 0 deletions modules/common/route/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

// +kubebuilder:object:generate:=true

package route

import (
Expand All @@ -27,6 +29,7 @@ type Route struct {
route *routev1.Route
timeout time.Duration
hostname string
override *OverrideSpec
}

// GenericRouteDetails -
Expand All @@ -38,3 +41,112 @@ type GenericRouteDetails struct {
TargetPortName string
FQDN string
}

// OverrideSpec configuration for the Route created to serve traffic to the cluster.
type OverrideSpec struct {
// +optional
*EmbeddedLabelsAnnotations `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec defines the behavior of a Route.
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
// +optional
Spec *Spec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
}

// EmbeddedLabelsAnnotations is an embedded subset of the fields included in k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta.
// Only labels and annotations are included.
type EmbeddedLabelsAnnotations struct {
// Map of string keys and values that can be used to organize and categorize
// (scope and select) objects. May match selectors of replication controllers
// and services.
// More info: http://kubernetes.io/docs/user-guide/labels
// +optional
Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"`

// Annotations is an unstructured key value map stored with a resource that may be
// set by external tools to store and retrieve arbitrary metadata. They are not
// queryable and should be preserved when modifying objects.
// More info: http://kubernetes.io/docs/user-guide/annotations
// +optional
Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"`
}

// Spec describes the hostname or path the route exposes, any security information,
// and one to four backends (services) the route points to. Requests are distributed
// among the backends depending on the weights assigned to each backend. When using
// roundrobin scheduling the portion of requests that go to each backend is the backend
// weight divided by the sum of all of the backend weights. When the backend has more than
// one endpoint the requests that end up on the backend are roundrobin distributed among
// the endpoints. Weights are between 0 and 256 with default 100. Weight 0 causes no requests
// to the backend. If all weights are zero the route will be considered to have no backends
// and return a standard 503 response.
//
// The `tls` field is optional and allows specific certificates or behavior for the
// route. Routers typically configure a default certificate on a wildcard domain to
// terminate routes without explicit certificates, but custom hostnames usually must
// choose passthrough (send traffic directly to the backend via the TLS Server-Name-
// Indication field) or provide a certificate.
//
// Copy of RouteSpec in https://github.com/openshift/api/blob/master/route/v1/types.go, all
// parameters set to optional.
type Spec struct {
// host is an alias/DNS that points to the service. Optional.
// If not specified a route name will typically be automatically
// chosen.
// Must follow DNS952 subdomain conventions.
//
// +optional
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`
Host string `json:"host,omitempty" protobuf:"bytes,1,opt,name=host"`
// subdomain is a DNS subdomain that is requested within the ingress controller's
// domain (as a subdomain). If host is set this field is ignored. An ingress
// controller may choose to ignore this suggested name, in which case the controller
// will report the assigned name in the status.ingress array or refuse to admit the
// route. If this value is set and the server does not support this field host will
// be populated automatically. Otherwise host is left empty. The field may have
// multiple parts separated by a dot, but not all ingress controllers may honor
// the request. This field may not be changed after creation except by a user with
// the update routes/custom-host permission.
//
// Example: subdomain `frontend` automatically receives the router subdomain
// `apps.mycluster.com` to have a full hostname `frontend.apps.mycluster.com`.
//
// +optional
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`
Subdomain string `json:"subdomain,omitempty" protobuf:"bytes,8,opt,name=subdomain"`

// path that the router watches for, to route traffic for to the service. Optional
//
// +optional
// +kubebuilder:validation:Pattern=`^/`
Path string `json:"path,omitempty" protobuf:"bytes,2,opt,name=path"`

// to is an object the route should use as the primary backend. Only the Service kind
// is allowed, and it will be defaulted to Service. If the weight field (0-256 default 100)
// is set to zero, no traffic will be sent to this backend.
// +optional
To routev1.RouteTargetReference `json:"to,omitempty" protobuf:"bytes,3,opt,name=to"`

// alternateBackends allows up to 3 additional backends to be assigned to the route.
// Only the Service kind is allowed, and it will be defaulted to Service.
// Use the weight field in RouteTargetReference object to specify relative preference.
//
// +kubebuilder:validation:MaxItems=3
AlternateBackends []routev1.RouteTargetReference `json:"alternateBackends,omitempty" protobuf:"bytes,4,rep,name=alternateBackends"`

// If specified, the port to be used by the router. Most routers will use all
// endpoints exposed by the service by default - set this value to instruct routers
// which port to use.
Port *routev1.RoutePort `json:"port,omitempty" protobuf:"bytes,5,opt,name=port"`

// The tls field provides the ability to configure certificates and termination for the route.
TLS *routev1.TLSConfig `json:"tls,omitempty" protobuf:"bytes,6,opt,name=tls"`

// Wildcard policy if any for the route.
// Currently only 'Subdomain' or 'None' is allowed.
//
// +kubebuilder:validation:Enum=None;Subdomain;""
// +kubebuilder:default=None
WildcardPolicy routev1.WildcardPolicyType `json:"wildcardPolicy,omitempty" protobuf:"bytes,7,opt,name=wildcardPolicy"`
}
Loading

0 comments on commit 7450e90

Please sign in to comment.