Skip to content

Commit d54fc81

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 d54fc81

9 files changed

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

0 commit comments

Comments
 (0)