Skip to content

Commit d6599c3

Browse files
committed
feat: add componentRoutes support to ROSAControlPlane
Add support for customizing console and downloads route hostnames and TLS certificates on ROSA HCP clusters via the ROSAControlPlane CRD. Signed-off-by: Cortney Reed <creed@redhat.com>
1 parent c553a81 commit d6599c3

9 files changed

Lines changed: 529 additions & 0 deletions

config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,37 @@ spec:
215215
type: array
216216
type: object
217217
type: object
218+
componentRoutes:
219+
description: componentRoutes allows customizing the hostname and TLS
220+
certificate for console and downloads routes.
221+
items:
222+
description: ComponentRouteSpec defines a custom hostname and TLS
223+
secret for a component route.
224+
properties:
225+
hostname:
226+
description: hostname is the custom domain for the component
227+
route (e.g. console.example.com).
228+
minLength: 1
229+
type: string
230+
name:
231+
description: name is the component route name. Valid values
232+
are "console" and "downloads".
233+
enum:
234+
- console
235+
- downloads
236+
type: string
237+
tlsSecretRef:
238+
description: tlsSecretRef is the name of the TLS secret in the
239+
openshift-config namespace on the hosted cluster.
240+
minLength: 1
241+
type: string
242+
required:
243+
- hostname
244+
- name
245+
- tlsSecretRef
246+
type: object
247+
maxItems: 2
248+
type: array
218249
controlPlaneEndpoint:
219250
description: ControlPlaneEndpoint represents the endpoint used to
220251
communicate with the control plane.

controlplane/rosa/api/v1beta2/rosacontrolplane_types.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ const (
104104
AutoNodeModeDisabled AutoNodeMode = "Disabled"
105105
)
106106

107+
// ComponentRouteKey specifies the name of a component route.
108+
type ComponentRouteKey string
109+
110+
const (
111+
// ComponentRouteConsole is the console component route.
112+
ComponentRouteConsole ComponentRouteKey = "console"
113+
114+
// ComponentRouteDownloads is the downloads component route.
115+
ComponentRouteDownloads ComponentRouteKey = "downloads"
116+
)
117+
107118
// RosaControlPlaneSpec defines the desired state of ROSAControlPlane.
108119
type RosaControlPlaneSpec struct { //nolint: maligned
109120
// Cluster name must be valid DNS-1035 label, so it must consist of lower case alphanumeric
@@ -354,6 +365,11 @@ type RosaControlPlaneSpec struct { //nolint: maligned
354365
// s3LogForwarder set the AWS S3 log forward config for applications and groupVersions.
355366
// +optional
356367
S3LogForwarder *S3LogForwarderConfig `json:"s3LogForwarder,omitempty"`
368+
369+
// componentRoutes allows customizing the hostname and TLS certificate for console and downloads routes.
370+
// +optional
371+
// +kubebuilder:validation:MaxItems=2
372+
ComponentRoutes []ComponentRouteSpec `json:"componentRoutes,omitempty"`
357373
}
358374

359375
// CloudWatchLogForwarderConfig present the cloudWatch log forward config for applications and groupVersions.
@@ -394,6 +410,24 @@ type S3LogForwarderConfig struct {
394410
S3ConfigBucketPrefix string `json:"s3ConfigBucketPrefix,omitempty"`
395411
}
396412

413+
// ComponentRouteSpec defines a custom hostname and TLS secret for a component route.
414+
type ComponentRouteSpec struct {
415+
// name is the component route name. Valid values are "console" and "downloads".
416+
// +kubebuilder:validation:Required
417+
// +kubebuilder:validation:Enum=console;downloads
418+
Name ComponentRouteKey `json:"name"`
419+
420+
// hostname is the custom domain for the component route (e.g. console.example.com).
421+
// +kubebuilder:validation:Required
422+
// +kubebuilder:validation:MinLength=1
423+
Hostname string `json:"hostname"`
424+
425+
// tlsSecretRef is the name of the TLS secret in the openshift-config namespace on the hosted cluster.
426+
// +kubebuilder:validation:Required
427+
// +kubebuilder:validation:MinLength=1
428+
TLSSecretRef string `json:"tlsSecretRef"`
429+
}
430+
397431
// AutoNode set the AutoNode mode and AutoNode role ARN.
398432
type AutoNode struct {
399433
// mode specifies the mode for the AutoNode. Setting Enable/Disable mode will allows/disallow karpenter AutoNode scaling.

controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/rosa/controllers/rosacontrolplane_controller.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,10 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc
333333
return ctrl.Result{RequeueAfter: time.Second * 60}, fmt.Errorf("failed to reconcile logForwarders: %w", err)
334334
}
335335

336+
if err := r.reconcileComponentRoutes(rosaScope, ocmClient, cluster); err != nil {
337+
return ctrl.Result{}, fmt.Errorf("failed to reconcile componentRoutes: %w", err)
338+
}
339+
336340
if err := r.updateOCMCluster(rosaScope, ocmClient, cluster, creator); err != nil {
337341
return ctrl.Result{}, fmt.Errorf("failed to update rosa control plane: %w", err)
338342
}
@@ -688,6 +692,88 @@ func buildGroups(ids []string) []*cmv1.LogForwarderGroupBuilder {
688692
return groups
689693
}
690694

695+
func (r *ROSAControlPlaneReconciler) reconcileComponentRoutes(rosaScope *scope.ROSAControlPlaneScope, ocmClient rosa.OCMClient, cluster *cmv1.Cluster) error {
696+
desiredRoutes := rosaScope.ControlPlane.Spec.ComponentRoutes
697+
698+
rosaScope.Info("reconcile componentRoutes")
699+
700+
ingresses, err := ocmClient.GetIngresses(cluster.ID())
701+
if err != nil {
702+
return fmt.Errorf("failed to get ingresses: %w", err)
703+
}
704+
705+
var defaultIngress *cmv1.Ingress
706+
for _, ing := range ingresses {
707+
if ing.Default() {
708+
defaultIngress = ing
709+
break
710+
}
711+
}
712+
if defaultIngress == nil {
713+
return fmt.Errorf("default ingress not found for cluster %s", cluster.ID())
714+
}
715+
716+
if componentRoutesEqual(defaultIngress.ComponentRoutes(), desiredRoutes) {
717+
return nil
718+
}
719+
720+
componentRoutes := resetComponentRoutes()
721+
for _, route := range desiredRoutes {
722+
componentRoutes[string(route.Name)] = cmv1.NewComponentRoute().
723+
Hostname(route.Hostname).
724+
TlsSecretRef(route.TLSSecretRef)
725+
}
726+
727+
updatedIngress, err := cmv1.NewIngress().
728+
ID(defaultIngress.ID()).
729+
ComponentRoutes(componentRoutes).
730+
Build()
731+
if err != nil {
732+
return fmt.Errorf("failed to build ingress: %w", err)
733+
}
734+
735+
rosaScope.Info(fmt.Sprintf("updating ingress %s componentRoutes on cluster %s",
736+
defaultIngress.ID(), cluster.ID()))
737+
_, err = ocmClient.UpdateIngress(cluster.ID(), updatedIngress)
738+
return err
739+
}
740+
741+
func resetComponentRoutes() map[string]*cmv1.ComponentRouteBuilder {
742+
keys := []rosacontrolplanev1.ComponentRouteKey{rosacontrolplanev1.ComponentRouteConsole, rosacontrolplanev1.ComponentRouteDownloads}
743+
routes := make(map[string]*cmv1.ComponentRouteBuilder, len(keys))
744+
for _, key := range keys {
745+
routes[string(key)] = cmv1.NewComponentRoute().Hostname("").TlsSecretRef("")
746+
}
747+
return routes
748+
}
749+
750+
func componentRoutesEqual(current map[string]*cmv1.ComponentRoute, desired []rosacontrolplanev1.ComponentRouteSpec) bool {
751+
desiredMap := make(map[string]rosacontrolplanev1.ComponentRouteSpec, len(desired))
752+
for _, route := range desired {
753+
desiredMap[string(route.Name)] = route
754+
}
755+
756+
keys := []rosacontrolplanev1.ComponentRouteKey{rosacontrolplanev1.ComponentRouteConsole, rosacontrolplanev1.ComponentRouteDownloads}
757+
for _, key := range keys {
758+
k := string(key)
759+
desiredRoute, desiredExists := desiredMap[k]
760+
currentRoute, currentExists := current[k]
761+
762+
if desiredExists {
763+
if !currentExists {
764+
return false
765+
}
766+
if currentRoute.Hostname() != desiredRoute.Hostname ||
767+
currentRoute.TlsSecretRef() != desiredRoute.TLSSecretRef {
768+
return false
769+
}
770+
} else if currentExists && (currentRoute.Hostname() != "" || currentRoute.TlsSecretRef() != "") {
771+
return false
772+
}
773+
}
774+
return true
775+
}
776+
691777
func (r *ROSAControlPlaneReconciler) reconcileClusterVersion(rosaScope *scope.ROSAControlPlaneScope, ocmClient rosa.OCMClient, cluster *cmv1.Cluster) error {
692778
version := rosaScope.ControlPlane.Spec.Version
693779
if version == rosa.RawVersionID(cluster.Version()) {

0 commit comments

Comments
 (0)