Skip to content

Commit

Permalink
Merge pull request #293 from stuggi/route_override
Browse files Browse the repository at this point in the history
Allow customize route via route.Override
  • Loading branch information
abays authored Jul 28, 2023
2 parents 13724f4 + a5afd03 commit 36c6d81
Show file tree
Hide file tree
Showing 8 changed files with 687 additions and 8 deletions.
8 changes: 7 additions & 1 deletion 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 @@ -174,7 +176,7 @@ func ExposeEndpoints(
if endpointType == EndpointPublic {
// Create the route
// TODO TLS
route := route.NewRoute(
route, err := route.NewRoute(
route.GenericRoute(&route.GenericRouteDetails{
Name: endpointName,
Namespace: h.GetBeforeObject().GetNamespace(),
Expand All @@ -184,7 +186,11 @@ func ExposeEndpoints(
}),
exportLabels,
timeout,
data.RouteOverride,
)
if err != nil {
return endpointMap, ctrl.Result{}, err
}

ctrlResult, err = route.CreateOrPatch(ctx, h)
if err != nil {
Expand Down
2 changes: 1 addition & 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.6 // indirect; indirect // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect; indirect // indirect
k8s.io/utils v0.0.0-20230711102312-30195339c3c7 // indirect; indirect // indirect
k8s.io/utils v0.0.0-20230711102312-30195339c3c7 // 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 Down
46 changes: 43 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,11 +39,49 @@ func NewRoute(
route *routev1.Route,
labels map[string]string,
timeout time.Duration,
) *Route {
return &Route{
override *OverrideSpec,
) (*Route, error) {
r := &Route{
route: route,
timeout: timeout,
}

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

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

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

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

return r, nil
}

// GetHostname - returns the hostname of the created route
Expand Down Expand Up @@ -89,7 +129,7 @@ func (r *Route) CreateOrPatch(
}

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
145 changes: 145 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 @@ -38,3 +40,146 @@ 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
//
// The spec will be merged using StrategicMergePatch
// - Provided parameters will override the ones from the original spec.
// - Required parameters of sub structs have to be named.
// - For parameters which are list of struct it depends on the patchStrategy defined on the list
// https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/#notes-on-the-strategic-merge-patch
// If `patchStrategy:"merge"` is set, src and dst list gets merged, otherwise they get replaced.
// +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.
// New labels/annotations get merged with the ones created by the operator. If a privided
// annotation/label is the same as one created by the service operator, the ones provided
// via this override will replace the one from the operator.
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,
// parameters set to be optional, have omitempty, and no default.
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.
To TargetReference `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 []TargetReference `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.
// +optional
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;""
WildcardPolicy routev1.WildcardPolicyType `json:"wildcardPolicy,omitempty" protobuf:"bytes,7,opt,name=wildcardPolicy"`
}

// TargetReference specifies the target that resolve into endpoints. Only the 'Service'
// kind is allowed. Use 'weight' field to emphasize one over others.
// Copy of RouteTargetReference in https://github.com/openshift/api/blob/master/route/v1/types.go,
// parameters set to be optional, have omitempty, and no default.
type TargetReference struct {
// The kind of target that the route is referring to. Currently, only 'Service' is allowed
//
// +optional
// +kubebuilder:validation:Enum=Service;""
Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`

// name of the service/target that is being referred to. e.g. name of the service
//
// +optional
Name string `json:"name,omitempty" protobuf:"bytes,2,opt,name=name"`

// weight as an integer between 0 and 256, default 100, that specifies the target's relative weight
// against other target reference objects. 0 suppresses requests to this backend.
//
// +optional
// +kubebuilder:validation:Minimum=0
// +kubebuilder:validation:Maximum=256
Weight *int32 `json:"weight,omitempty" protobuf:"varint,3,opt,name=weight"`
}
Loading

0 comments on commit 36c6d81

Please sign in to comment.