Skip to content

Commit 3f9bd3a

Browse files
committed
Use RoleConfigRef in Rosa Control Plane
1 parent 7395772 commit 3f9bd3a

File tree

8 files changed

+140
-25
lines changed

8 files changed

+140
-25
lines changed

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

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -523,8 +523,9 @@ spec:
523523
- name
524524
type: object
525525
installerRoleARN:
526-
description: InstallerRoleARN is an AWS IAM role that OpenShift Cluster
527-
Manager will assume to create the cluster..
526+
description: |-
527+
InstallerRoleARN is an AWS IAM role that OpenShift Cluster Manager will assume to create the cluster.
528+
Required if RosaRoleConfigRef is not specified.
528529
type: string
529530
network:
530531
description: Network config for the ROSA HCP cluster.
@@ -558,7 +559,9 @@ spec:
558559
type: string
559560
type: object
560561
oidcID:
561-
description: The ID of the internal OpenID Connect Provider.
562+
description: |-
563+
The ID of the internal OpenID Connect Provider.
564+
Required if RosaRoleConfigRef is not specified.
562565
type: string
563566
x-kubernetes-validations:
564567
- message: oidcID is immutable
@@ -574,8 +577,9 @@ spec:
574577
description: The AWS Region the cluster lives in.
575578
type: string
576579
rolesRef:
577-
description: AWS IAM roles used to perform credential requests by
578-
the openshift operators.
580+
description: |-
581+
AWS IAM roles used to perform credential requests by the openshift operators.
582+
Required if RosaRoleConfigRef is not specified.
579583
properties:
580584
controlPlaneOperatorARN:
581585
description: "ControlPlaneOperatorARN is an ARN value referencing
@@ -775,6 +779,22 @@ spec:
775779
x-kubernetes-validations:
776780
- message: rosaClusterName is immutable
777781
rule: self == oldSelf
782+
rosaRoleConfigRef:
783+
description: |-
784+
RosaRoleConfigRef is a reference to a RosaRoleConfig resource that contains account and operator roles and OIDC configuration.
785+
If specified, the roles and OIDC configuration will be taken from the referenced RosaRoleConfig instead of the direct fields.
786+
properties:
787+
name:
788+
default: ""
789+
description: |-
790+
Name of the referent.
791+
This field is effectively required, but due to backwards compatibility is
792+
allowed to be empty. Instances of this type with an empty value here are
793+
almost certainly wrong.
794+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
795+
type: string
796+
type: object
797+
x-kubernetes-map-type: atomic
778798
subnets:
779799
description: |-
780800
The Subnet IDs to use when installing the cluster.
@@ -786,6 +806,7 @@ spec:
786806
description: |-
787807
SupportRoleARN is an AWS IAM role used by Red Hat SREs to enable
788808
access to the cluster account in order to provide support.
809+
Required if RosaRoleConfigRef is not specified.
789810
type: string
790811
version:
791812
description: OpenShift semantic version, for example "4.14.5".
@@ -804,22 +825,18 @@ spec:
804825
- AlwaysAcknowledge
805826
type: string
806827
workerRoleARN:
807-
description: WorkerRoleARN is an AWS IAM role that will be attached
808-
to worker instances.
828+
description: |-
829+
WorkerRoleARN is an AWS IAM role that will be attached to worker instances.
830+
Required if RosaRoleConfigRef is not specified.
809831
type: string
810832
required:
811833
- availabilityZones
812834
- channelGroup
813-
- installerRoleARN
814-
- oidcID
815835
- region
816-
- rolesRef
817836
- rosaClusterName
818837
- subnets
819-
- supportRoleARN
820838
- version
821839
- versionGate
822-
- workerRoleARN
823840
type: object
824841
status:
825842
description: RosaControlPlaneStatus defines the observed state of ROSAControlPlane.

config/crd/patches/cainjection_in_rosaroleconfigs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# The following patch adds a directive for certmanager to inject CA into the CRD
22
# CRD conversion requires k8s 1.13 or later.
3-
apiVersion: apiextensions.k8s.io/v1beta1
3+
apiVersion: apiextensions.k8s.io/v1
44
kind: CustomResourceDefinition
55
metadata:
66
annotations:
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
# The following patch enables conversion webhook for CRD
22
# CRD conversion requires k8s 1.13 or later.
3-
apiVersion: apiextensions.k8s.io/v1beta1
3+
apiVersion: apiextensions.k8s.io/v1
44
kind: CustomResourceDefinition
55
metadata:
66
name: rosaroleconfigs.infrastructure.cluster.x-k8s.io
77
spec:
88
conversion:
99
strategy: Webhook
10-
webhookClientConfig:
11-
service:
12-
namespace: system
13-
name: webhook-service
14-
path: /convert
10+
webhook:
11+
conversionReviewVersions: ["v1", "v1beta1"]
12+
clientConfig:
13+
service:
14+
namespace: system
15+
name: webhook-service
16+
path: /convert

controlplane/rosa/api/v1beta2/conditions_consts.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const (
3131
// ExternalAuthConfiguredCondition condition reports whether external auth has beed correctly configured.
3232
ExternalAuthConfiguredCondition clusterv1.ConditionType = "ExternalAuthConfigured"
3333

34+
// ROSARoleConfigReadyCondition condition reports whether the referenced RosaRoleConfig is ready.
35+
ROSARoleConfigReadyCondition clusterv1.ConditionType = "ROSARoleConfigReady"
36+
3437
// ReconciliationFailedReason used to report reconciliation failures.
3538
ReconciliationFailedReason = "ReconciliationFailed"
3639

@@ -39,4 +42,10 @@ const (
3942

4043
// ROSAControlPlaneInvalidConfigurationReason used to report invalid user input.
4144
ROSAControlPlaneInvalidConfigurationReason = "InvalidConfiguration"
45+
46+
// ROSARoleConfigNotReadyReason used to report when referenced RosaRoleConfig is not ready.
47+
ROSARoleConfigNotReadyReason = "ROSARoleConfigNotReady"
48+
49+
// ROSARoleConfigNotFoundReason used to report when referenced RosaRoleConfig is not found.
50+
ROSARoleConfigNotFoundReason = "ROSARoleConfigNotFound"
4251
)

controlplane/rosa/api/v1beta2/rosacontrolplane_types.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,23 @@ type RosaControlPlaneSpec struct { //nolint: maligned
120120
// +kubebuilder:default=WaitForAcknowledge
121121
VersionGate VersionGateAckType `json:"versionGate"`
122122

123+
// RosaRoleConfigRef is a reference to a RosaRoleConfig resource that contains account and operator roles and OIDC configuration.
124+
// If specified, the roles and OIDC configuration will be taken from the referenced RosaRoleConfig instead of the direct fields.
125+
//
126+
// +optional
127+
RosaRoleConfigRef *corev1.LocalObjectReference `json:"rosaRoleConfigRef,omitempty"`
128+
123129
// AWS IAM roles used to perform credential requests by the openshift operators.
124-
RolesRef AWSRolesRef `json:"rolesRef"`
130+
// Required if RosaRoleConfigRef is not specified.
131+
// +optional
132+
RolesRef AWSRolesRef `json:"rolesRef,omitempty"`
125133

126134
// The ID of the internal OpenID Connect Provider.
135+
// Required if RosaRoleConfigRef is not specified.
127136
//
128137
// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="oidcID is immutable"
129-
OIDCID string `json:"oidcID"`
138+
// +optional
139+
OIDCID string `json:"oidcID,omitempty"`
130140

131141
// EnableExternalAuthProviders enables external authentication configuration for the cluster.
132142
//
@@ -145,13 +155,19 @@ type RosaControlPlaneSpec struct { //nolint: maligned
145155
// +kubebuilder:validation:MaxItems=1
146156
ExternalAuthProviders []ExternalAuthProvider `json:"externalAuthProviders,omitempty"`
147157

148-
// InstallerRoleARN is an AWS IAM role that OpenShift Cluster Manager will assume to create the cluster..
149-
InstallerRoleARN string `json:"installerRoleARN"`
158+
// InstallerRoleARN is an AWS IAM role that OpenShift Cluster Manager will assume to create the cluster.
159+
// Required if RosaRoleConfigRef is not specified.
160+
// +optional
161+
InstallerRoleARN string `json:"installerRoleARN,omitempty"`
150162
// SupportRoleARN is an AWS IAM role used by Red Hat SREs to enable
151163
// access to the cluster account in order to provide support.
152-
SupportRoleARN string `json:"supportRoleARN"`
164+
// Required if RosaRoleConfigRef is not specified.
165+
// +optional
166+
SupportRoleARN string `json:"supportRoleARN,omitempty"`
153167
// WorkerRoleARN is an AWS IAM role that will be attached to worker instances.
154-
WorkerRoleARN string `json:"workerRoleARN"`
168+
// Required if RosaRoleConfigRef is not specified.
169+
// +optional
170+
WorkerRoleARN string `json:"workerRoleARN,omitempty"`
155171

156172
// BillingAccount is an optional AWS account to use for billing the subscription fees for ROSA HCP clusters.
157173
// The cost of running each ROSA HCP cluster will be billed to the infrastructure account in which the cluster

controlplane/rosa/api/v1beta2/rosacontrolplane_webhook.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ func (*rosaControlPlaneWebhook) ValidateCreate(_ context.Context, obj runtime.Ob
5858
allErrs = append(allErrs, err)
5959
}
6060

61+
if err := r.validateRosaRoleConfig(); err != nil {
62+
allErrs = append(allErrs, err)
63+
}
64+
6165
allErrs = append(allErrs, r.validateNetwork()...)
6266
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
6367

@@ -179,6 +183,24 @@ func (r *ROSAControlPlane) validateExternalAuthProviders() *field.Error {
179183
return nil
180184
}
181185

186+
func (r *ROSAControlPlane) validateRosaRoleConfig() *field.Error {
187+
hasRosaRoleConfigRef := r.Spec.RosaRoleConfigRef != nil
188+
hasDirectRoleFields := r.Spec.OIDCID != "" || r.Spec.InstallerRoleARN != "" || r.Spec.SupportRoleARN != "" || r.Spec.WorkerRoleARN != "" ||
189+
r.Spec.RolesRef.IngressARN != "" || r.Spec.RolesRef.ImageRegistryARN != "" || r.Spec.RolesRef.StorageARN != "" ||
190+
r.Spec.RolesRef.NetworkARN != "" || r.Spec.RolesRef.KubeCloudControllerARN != "" || r.Spec.RolesRef.NodePoolManagementARN != "" ||
191+
r.Spec.RolesRef.ControlPlaneOperatorARN != "" || r.Spec.RolesRef.KMSProviderARN != ""
192+
193+
if hasRosaRoleConfigRef && hasDirectRoleFields {
194+
return field.Invalid(field.NewPath("spec.rosaRoleConfigRef"), r.Spec.RosaRoleConfigRef, "rosaRoleConfigRef and direct role fields (oidcID, installerRoleARN, supportRoleARN, workerRoleARN, rolesRef) are mutually exclusive")
195+
}
196+
197+
if !hasRosaRoleConfigRef && !hasDirectRoleFields {
198+
return field.Invalid(field.NewPath("spec"), r.Spec, "either rosaRoleConfigRef or direct role fields (oidcID, installerRoleARN, supportRoleARN, workerRoleARN, rolesRef) must be specified")
199+
}
200+
201+
return nil
202+
}
203+
182204
// Default implements admission.Defaulter.
183205
func (*rosaControlPlaneWebhook) Default(_ context.Context, obj runtime.Object) error {
184206
r, ok := obj.(*ROSAControlPlane)

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

Lines changed: 5 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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ func (r *ROSAControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr c
143143
// +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes,verbs=get;list;watch;update;patch;delete
144144
// +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes/status,verbs=get;update;patch
145145
// +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes/finalizers,verbs=update
146+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaroleconfigs,verbs=get;list;watch;
147+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaroleconfigs/status,verbs=get;
146148

147149
// Reconcile will reconcile RosaControlPlane Resources.
148150
func (r *ROSAControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, reterr error) {
@@ -226,6 +228,48 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc
226228
return ctrl.Result{}, fmt.Errorf("failed to transform caller identity to creator: %w", err)
227229
}
228230

231+
// Get role configuration from either RosaRoleConfig or direct fields
232+
if rosaScope.ControlPlane.Spec.RosaRoleConfigRef != nil {
233+
// Get configuration from RosaRoleConfig
234+
rosaRoleConfig := &expinfrav1.ROSARoleConfig{}
235+
key := client.ObjectKey{
236+
Name: rosaScope.ControlPlane.Spec.RosaRoleConfigRef.Name,
237+
Namespace: rosaScope.ControlPlane.Namespace,
238+
}
239+
240+
if err := r.Client.Get(ctx, key, rosaRoleConfig); err != nil {
241+
if apierrors.IsNotFound(err) {
242+
conditions.MarkFalse(rosaScope.ControlPlane,
243+
rosacontrolplanev1.ROSARoleConfigReadyCondition,
244+
rosacontrolplanev1.ROSARoleConfigNotFoundReason,
245+
clusterv1.ConditionSeverityError,
246+
"RosaRoleConfig %s/%s not found", rosaScope.ControlPlane.Namespace, rosaScope.ControlPlane.Spec.RosaRoleConfigRef.Name)
247+
return ctrl.Result{}, fmt.Errorf("RosaRoleConfig %s/%s not found: %w", rosaScope.ControlPlane.Namespace, rosaScope.ControlPlane.Spec.RosaRoleConfigRef.Name, err)
248+
}
249+
return ctrl.Result{}, fmt.Errorf("failed to get RosaRoleConfig %s/%s: %w", rosaScope.ControlPlane.Namespace, rosaScope.ControlPlane.Spec.RosaRoleConfigRef.Name, err)
250+
}
251+
252+
// Check if RosaRoleConfig is ready
253+
if !conditions.IsTrue(rosaRoleConfig, expinfrav1.RosaRoleConfigReadyCondition) {
254+
conditions.MarkFalse(rosaScope.ControlPlane,
255+
rosacontrolplanev1.ROSARoleConfigReadyCondition,
256+
rosacontrolplanev1.ROSARoleConfigNotReadyReason,
257+
clusterv1.ConditionSeverityWarning,
258+
"RosaRoleConfig %s/%s is not ready", rosaScope.ControlPlane.Namespace, rosaScope.ControlPlane.Spec.RosaRoleConfigRef.Name)
259+
return ctrl.Result{}, fmt.Errorf("RosaRoleConfig %s/%s is not ready", rosaScope.ControlPlane.Namespace, rosaScope.ControlPlane.Spec.RosaRoleConfigRef.Name)
260+
}
261+
262+
conditions.MarkTrue(rosaScope.ControlPlane, rosacontrolplanev1.ROSARoleConfigReadyCondition)
263+
264+
// Update spec fields from RosaRoleConfig
265+
rosaScope.ControlPlane.Spec.OIDCID = rosaRoleConfig.Status.OIDCID
266+
rosaScope.ControlPlane.Spec.InstallerRoleARN = rosaRoleConfig.Status.AccountRolesRef.InstallerRoleARN
267+
rosaScope.ControlPlane.Spec.SupportRoleARN = rosaRoleConfig.Status.AccountRolesRef.SupportRoleARN
268+
rosaScope.ControlPlane.Spec.WorkerRoleARN = rosaRoleConfig.Status.AccountRolesRef.WorkerRoleARN
269+
rosaScope.ControlPlane.Spec.RolesRef = rosaRoleConfig.Status.OperatorRolesRef
270+
rosaScope.ControlPlane.Spec.EnableExternalAuthProviders = len(rosaRoleConfig.Spec.OIDCConfig.ExternalAuthProviders) > 0
271+
}
272+
229273
validationMessage, err := validateControlPlaneSpec(ocmClient, rosaScope)
230274
if err != nil {
231275
return ctrl.Result{}, fmt.Errorf("failed to validate ROSAControlPlane.spec: %w", err)

0 commit comments

Comments
 (0)