diff --git a/Makefile b/Makefile index c83ceff0..c401069d 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/modules/common/endpoint/endpoint.go b/modules/common/endpoint/endpoint.go index 6f1d0394..32c34e9f 100644 --- a/modules/common/endpoint/endpoint.go +++ b/modules/common/endpoint/endpoint.go @@ -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 @@ -184,6 +186,7 @@ func ExposeEndpoints( }), exportLabels, timeout, + data.RouteOverride, ) ctrlResult, err = route.CreateOrPatch(ctx, h) diff --git a/modules/common/go.mod b/modules/common/go.mod index 1ad4be3b..4c114d99 100644 --- a/modules/common/go.mod +++ b/modules/common/go.mod @@ -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 @@ -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 diff --git a/modules/common/go.sum b/modules/common/go.sum index d5527d57..1a260101 100644 --- a/modules/common/go.sum +++ b/modules/common/go.sum @@ -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= diff --git a/modules/common/route/route.go b/modules/common/route/route.go index 016e2e54..12f22d7e 100644 --- a/modules/common/route/route.go +++ b/modules/common/route/route.go @@ -18,6 +18,7 @@ package route import ( "context" + "encoding/json" "fmt" "time" @@ -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" ) @@ -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, } } @@ -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 { diff --git a/modules/common/route/types.go b/modules/common/route/types.go index 7a2e9171..8e6991a6 100644 --- a/modules/common/route/types.go +++ b/modules/common/route/types.go @@ -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 ( @@ -27,6 +29,7 @@ type Route struct { route *routev1.Route timeout time.Duration hostname string + override *OverrideSpec } // GenericRouteDetails - @@ -38,3 +41,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"` +} diff --git a/modules/common/route/zz_generated.deepcopy.go b/modules/common/route/zz_generated.deepcopy.go new file mode 100644 index 00000000..5fe2a0be --- /dev/null +++ b/modules/common/route/zz_generated.deepcopy.go @@ -0,0 +1,180 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* + + +Licensed 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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package route + +import ( + "github.com/openshift/api/route/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmbeddedLabelsAnnotations) DeepCopyInto(out *EmbeddedLabelsAnnotations) { + *out = *in + 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.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmbeddedLabelsAnnotations. +func (in *EmbeddedLabelsAnnotations) DeepCopy() *EmbeddedLabelsAnnotations { + if in == nil { + return nil + } + out := new(EmbeddedLabelsAnnotations) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GenericRouteDetails) DeepCopyInto(out *GenericRouteDetails) { + *out = *in + 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 + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericRouteDetails. +func (in *GenericRouteDetails) DeepCopy() *GenericRouteDetails { + if in == nil { + return nil + } + out := new(GenericRouteDetails) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OverrideSpec) DeepCopyInto(out *OverrideSpec) { + *out = *in + if in.EmbeddedLabelsAnnotations != nil { + in, out := &in.EmbeddedLabelsAnnotations, &out.EmbeddedLabelsAnnotations + *out = new(EmbeddedLabelsAnnotations) + (*in).DeepCopyInto(*out) + } + if in.Spec != nil { + in, out := &in.Spec, &out.Spec + *out = new(Spec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OverrideSpec. +func (in *OverrideSpec) DeepCopy() *OverrideSpec { + if in == nil { + return nil + } + out := new(OverrideSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route) DeepCopyInto(out *Route) { + *out = *in + if in.route != nil { + in, out := &in.route, &out.route + *out = new(v1.Route) + (*in).DeepCopyInto(*out) + } + if in.override != nil { + in, out := &in.override, &out.override + *out = new(OverrideSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route. +func (in *Route) DeepCopy() *Route { + if in == nil { + return nil + } + out := new(Route) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Spec) DeepCopyInto(out *Spec) { + *out = *in + in.To.DeepCopyInto(&out.To) + if in.AlternateBackends != nil { + in, out := &in.AlternateBackends, &out.AlternateBackends + *out = make([]TargetReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Port != nil { + in, out := &in.Port, &out.Port + *out = new(v1.RoutePort) + **out = **in + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(v1.TLSConfig) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Spec. +func (in *Spec) DeepCopy() *Spec { + if in == nil { + return nil + } + out := new(Spec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetReference) DeepCopyInto(out *TargetReference) { + *out = *in + if in.Weight != nil { + in, out := &in.Weight, &out.Weight + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetReference. +func (in *TargetReference) DeepCopy() *TargetReference { + if in == nil { + return nil + } + out := new(TargetReference) + in.DeepCopyInto(out) + return out +} diff --git a/modules/common/test/functional/route_test.go b/modules/common/test/functional/route_test.go new file mode 100644 index 00000000..383950ea --- /dev/null +++ b/modules/common/test/functional/route_test.go @@ -0,0 +1,295 @@ +/* +Copyright 2023 Red Hat + +Licensed 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 functional + +import ( + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/openstack-k8s-operators/lib-common/modules/common/route" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" + + routev1 "github.com/openshift/api/route/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func getExampleRoute(namespace string) *routev1.Route { + return &routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: namespace, + Labels: map[string]string{ + "label": "a", + "replace": "a", + }, + Annotations: map[string]string{ + "anno": "a", + "replace": "a", + }, + }, + Spec: routev1.RouteSpec{ + Host: "some.host.svc", + Port: &routev1.RoutePort{ + TargetPort: intstr.FromInt(80), + }, + To: routev1.RouteTargetReference{ + Kind: "Service", + Name: "my-service", + Weight: pointer.Int32(100), + }, + }, + } +} + +var _ = Describe("route package", func() { + var namespace string + + BeforeEach(func() { + // NOTE(gibi): We need to create a unique namespace for each test run + // as namespaces cannot be deleted in a locally running envtest. See + // https://book.kubebuilder.io/reference/envtest.html#namespace-usage-limitation + namespace = uuid.New().String() + th.CreateNamespace(namespace) + // We still request the delete of the Namespace to properly cleanup if + // we run the test in an existing cluster. + DeferCleanup(th.DeleteNamespace, namespace) + + }) + + It("creates route with defaults", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{}, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + Expect(rv1.Annotations["anno"]).To(Equal("a")) + Expect(rv1.Annotations["replace"]).To(Equal("a")) + Expect(rv1.Labels["label"]).To(Equal("a")) + Expect(rv1.Labels["replace"]).To(Equal("a")) + Expect(rv1.Spec.Host).To(Equal("some.host.svc")) + Expect(rv1.Spec.Port.TargetPort.IntVal).To(Equal(int32(80))) + Expect(rv1.Spec.To.Name).To(Equal("my-service")) + Expect(*rv1.Spec.To.Weight).To(Equal(int32(100))) + + }) + + It("merges labels to the route", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{ + EmbeddedLabelsAnnotations: &route.EmbeddedLabelsAnnotations{ + Labels: map[string]string{ + "foo": "b", + "replace": "b", + }, + }, + }, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + // non overridden label exists + Expect(rv1.Labels["label"]).To(Equal("a")) + // adds new label + Expect(rv1.Labels["foo"]).To(Equal("b")) + // override replaces existing label + Expect(rv1.Labels["replace"]).To(Equal("b")) + }) + + It("merges annotations to the route", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{ + EmbeddedLabelsAnnotations: &route.EmbeddedLabelsAnnotations{ + Annotations: map[string]string{ + "foo": "b", + "replace": "b", + }, + }, + }, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + // non overridden annotation exists + Expect(rv1.Annotations["anno"]).To(Equal("a")) + // adds new annotation + Expect(rv1.Annotations["foo"]).To(Equal("b")) + // override replaces existing annotation + Expect(rv1.Annotations["replace"]).To(Equal("b")) + }) + + It("overrides spec.host if specified", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{ + Spec: &route.Spec{ + Host: "custom.host.domain", + }, + }, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + Expect(rv1.Spec.Host).To(Equal("custom.host.domain")) + }) + + It("overrides spec.subdomain if specified", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{ + Spec: &route.Spec{ + Subdomain: "subdomain", + }, + }, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + Expect(rv1.Spec.Subdomain).To(Equal("subdomain")) + }) + + It("overrides spec.path if specified", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{ + Spec: &route.Spec{ + Path: "/some/path", + }, + }, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + Expect(rv1.Spec.Path).To(Equal("/some/path")) + }) + + It("overrides spec.to if specified", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{ + Spec: &route.Spec{ + To: route.TargetReference{ + Name: "my-custom-service", + Weight: pointer.Int32(10), + }, + }, + }, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + Expect(rv1.Spec.To.Kind).To(Equal("Service")) + Expect(rv1.Spec.To.Name).To(Equal("my-custom-service")) + Expect(*rv1.Spec.To.Weight).To(Equal(int32(10))) + }) + + It("overrides spec.alternateBackends if specified", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{ + Spec: &route.Spec{ + AlternateBackends: []route.TargetReference{ + { + Kind: "Service", + Name: "my-alternate-service", + Weight: pointer.Int32(200), + }, + }, + }, + }, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + Expect(rv1.Spec.AlternateBackends[0].Name).To(Equal("my-alternate-service")) + Expect(*rv1.Spec.AlternateBackends[0].Weight).To(Equal(int32(200))) + }) + + It("overrides spec.port if specified", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{ + Spec: &route.Spec{ + Port: &routev1.RoutePort{ + TargetPort: intstr.FromInt(8080), + }, + }, + }, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + Expect(rv1.Spec.Port.TargetPort.IntVal).To(Equal(int32(8080))) + }) + + It("overrides spec.tls if specified", func() { + r := route.NewRoute( + getExampleRoute(namespace), + map[string]string{}, + timeout, + &route.OverrideSpec{ + Spec: &route.Spec{ + TLS: &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + Certificate: "cert", + Key: "key", + CACertificate: "cacert", + }, + }, + }, + ) + + _, err := r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + Expect(rv1.Spec.TLS.Termination).To(Equal(routev1.TLSTerminationEdge)) + Expect(rv1.Spec.TLS.Certificate).To(Equal("cert")) + Expect(rv1.Spec.TLS.Key).To(Equal("key")) + Expect(rv1.Spec.TLS.CACertificate).To(Equal("cacert")) + }) +}) diff --git a/modules/common/test/functional/suite_test.go b/modules/common/test/functional/suite_test.go index 81ab5ccc..6c9f5b1b 100644 --- a/modules/common/test/functional/suite_test.go +++ b/modules/common/test/functional/suite_test.go @@ -18,6 +18,7 @@ package functional import ( "context" + "path/filepath" "testing" "time" @@ -36,6 +37,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + routev1 "github.com/openshift/api/route/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -82,9 +84,13 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "..", "test", "openshift_crds", "route", "v1"), + }, ErrorIfCRDPathMissing: true, } var err error + // cfg is defined in this file globally. cfg, err = testEnv.Start() Expect(err).NotTo(HaveOccurred()) @@ -94,6 +100,8 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) err = corev1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = routev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme logger = ctrl.Log.WithName("---Test---") diff --git a/modules/openstack/go.mod b/modules/openstack/go.mod index 8a427026..ba272ec6 100644 --- a/modules/openstack/go.mod +++ b/modules/openstack/go.mod @@ -67,3 +67,7 @@ require ( ) replace github.com/openstack-k8s-operators/lib-common/modules/common => ../common + +// 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 diff --git a/modules/openstack/go.sum b/modules/openstack/go.sum index f9273b09..6af51bb7 100644 --- a/modules/openstack/go.sum +++ b/modules/openstack/go.sum @@ -221,8 +221,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -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/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/modules/test/go.mod b/modules/test/go.mod index eed94d5e..45b8bfd6 100644 --- a/modules/test/go.mod +++ b/modules/test/go.mod @@ -9,7 +9,7 @@ require ( github.com/onsi/gomega v1.27.6 github.com/openstack-k8s-operators/infra-operator/apis v0.0.0-20230609175832-5a9a30056080 github.com/openstack-k8s-operators/keystone-operator/api v0.0.0-20230612072624-8ebcfc19377a - github.com/openstack-k8s-operators/lib-common/modules/common v0.0.0-20230606033311-3b01713e4d45 + github.com/openstack-k8s-operators/lib-common/modules/common v0.0.0-20230707063813-c894bf75835d github.com/openstack-k8s-operators/mariadb-operator/api v0.0.0-20230602100742-579cb85d242d golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 golang.org/x/mod v0.9.0 @@ -18,7 +18,11 @@ require ( sigs.k8s.io/controller-runtime v0.14.6 ) -require github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 // indirect +require ( + github.com/imdario/mergo v0.3.15 // indirect + github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect +) require ( github.com/beorn7/perks v1.0.1 // indirect @@ -36,11 +40,9 @@ require ( github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/imdario/mergo v0.3.15 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -76,3 +78,7 @@ require ( ) replace github.com/openstack-k8s-operators/lib-common/modules/common => ../common + +// 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 diff --git a/modules/test/go.sum b/modules/test/go.sum index 22c587f5..4a8ab7c3 100644 --- a/modules/test/go.sum +++ b/modules/test/go.sum @@ -225,8 +225,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= 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/openstack-k8s-operators/infra-operator/apis v0.0.0-20230609175832-5a9a30056080 h1:wsBYp8qy5tFPKkk/nmqFUJV6cBIWfHwAPr4St/Oehr0= github.com/openstack-k8s-operators/infra-operator/apis v0.0.0-20230609175832-5a9a30056080/go.mod h1:KDC8rS9D00e4ud5iQUexUxtApmCgqTwjOKcHv2OhGiY= github.com/openstack-k8s-operators/keystone-operator/api v0.0.0-20230612072624-8ebcfc19377a h1:fLQSwMRvNO/o8hKXy20WmffVOnEyPOSKNpQEIhsBbDA=