From 27cfdc5efc0736cc1db71c960875ed1a81095882 Mon Sep 17 00:00:00 2001 From: Xiangjing Li Date: Wed, 28 Feb 2024 19:03:35 -0700 Subject: [PATCH 1/4] rosa: add etcdEncryption, tags Signed-off-by: Steve Kuznetsov --- ...lplane.cluster.x-k8s.io_rosacontrolplanes.yaml | 15 +++++++++++++++ .../rosa/api/v1beta2/rosacontrolplane_types.go | 13 +++++++++++++ .../rosa/api/v1beta2/zz_generated.deepcopy.go | 7 +++++++ .../controllers/rosacontrolplane_controller.go | 3 +++ 4 files changed, 38 insertions(+) diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml index d67cf97022..13aeaec335 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml @@ -45,6 +45,12 @@ spec: type: object spec: properties: + additionalTags: + additionalProperties: + type: string + description: AdditionalTags are user-defined tags to be added on the + AWS resources associated with the control plane. + type: object autoscaling: description: Autoscaling specifies auto scaling behaviour for the MachinePools. @@ -100,6 +106,15 @@ spec: type: string type: object x-kubernetes-map-type: atomic + etcdEncryptionKMSArn: + description: EtcdEncryptionKMSArn is the ARN of the KMS key used to + encrypt etcd. The key itself needs to be created out-of-band by + the user and tagged with `red-hat:true`. + maxLength: 2048 + type: string + x-kubernetes-validations: + - message: etcdEncryptionKMSArn must be a valid encryption key ARN + rule: self.matches('^arn:aws[\\w-]*:kms:[\\w-]+:\\d{12}:key\\/(mrk-[0-9a-f]{32}$|[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$)') identityRef: description: IdentityRef is a reference to an identity to be used when reconciling the managed control plane. If no identity is specified, diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go index 65ecd9279b..5390f25aff 100644 --- a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go +++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go @@ -99,6 +99,19 @@ type RosaControlPlaneSpec struct { //nolint: maligned // Autoscaling specifies auto scaling behaviour for the MachinePools. // +optional Autoscaling *expinfrav1.RosaMachinePoolAutoScaling `json:"autoscaling,omitempty"` + + // +kubebuilder:validation:Optional + + // AdditionalTags are user-defined tags to be added on the AWS resources associated with the control plane. + AdditionalTags infrav1.Tags `json:"additionalTags,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:MaxLength=2048 + // +kubebuilder:validation:XValidation:rule=`self.matches('^arn:aws[\\w-]*:kms:[\\w-]+:\\d{12}:key\\/(mrk-[0-9a-f]{32}$|[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$)')`, message="etcdEncryptionKMSArn must be a valid encryption key ARN" + + // EtcdEncryptionKMSArn is the ARN of the KMS key used to encrypt etcd. The key itself needs to be + // created out-of-band by the user and tagged with `red-hat:true`. + EtcdEncryptionKMSArn string `json:"etcdEncryptionKMSArn,omitempty"` } // NetworkSpec for ROSA-HCP. diff --git a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go index 41dac04354..d1403b9728 100644 --- a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go @@ -177,6 +177,13 @@ func (in *RosaControlPlaneSpec) DeepCopyInto(out *RosaControlPlaneSpec) { *out = new(expapiv1beta2.RosaMachinePoolAutoScaling) **out = **in } + if in.AdditionalTags != nil { + in, out := &in.AdditionalTags, &out.AdditionalTags + *out = make(apiv1beta2.Tags, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaControlPlaneSpec. diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 69e6e31c8a..9bc6594210 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -278,6 +278,9 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc DisableWorkloadMonitoring: ptr.To(true), DefaultIngress: ocm.NewDefaultIngressSpec(), // n.b. this is a no-op when it's set to the default value ComputeMachineType: rosaScope.ControlPlane.Spec.InstanceType, + Tags: rosaScope.ControlPlane.Spec.AdditionalTags, + EtcdEncryption: rosaScope.ControlPlane.Spec.EtcdEncryptionKMSArn != "", + EtcdEncryptionKMSArn: rosaScope.ControlPlane.Spec.EtcdEncryptionKMSArn, SubnetIds: rosaScope.ControlPlane.Spec.Subnets, AvailabilityZones: rosaScope.ControlPlane.Spec.AvailabilityZones, From 8dc29e534acb0833ea966fb0f2dc42dbe205c30d Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Wed, 28 Feb 2024 19:10:41 -0700 Subject: [PATCH 2/4] rosa: support PrivateLink Signed-off-by: Steve Kuznetsov --- .../controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml | 7 +++++++ controlplane/rosa/api/v1beta2/rosacontrolplane_types.go | 7 +++++++ controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go | 5 +++++ .../rosa/controllers/rosacontrolplane_controller.go | 2 ++ 4 files changed, 21 insertions(+) diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml index 13aeaec335..dcfa2419c1 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml @@ -177,6 +177,13 @@ spec: oidcID: description: The ID of the OpenID Connect Provider. type: string + private: + description: Private restricts master API endpoint and application + routes to direct, private connectivity. Traffic to these endpoints + will use AWS PrivateLink to have connectivity between VPCs, AWS + services, and your on-premises networks without exposing your traffic + to the public internet. + type: boolean region: description: The AWS Region the cluster lives in. type: string diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go index 5390f25aff..e590254ee8 100644 --- a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go +++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go @@ -112,6 +112,13 @@ type RosaControlPlaneSpec struct { //nolint: maligned // EtcdEncryptionKMSArn is the ARN of the KMS key used to encrypt etcd. The key itself needs to be // created out-of-band by the user and tagged with `red-hat:true`. EtcdEncryptionKMSArn string `json:"etcdEncryptionKMSArn,omitempty"` + + // +kubebuilder:validation:Optional + + // Private restricts master API endpoint and application routes to direct, private connectivity. + // Traffic to these endpoints will use AWS PrivateLink to have connectivity between VPCs, AWS services, + // and your on-premises networks without exposing your traffic to the public internet. + Private *bool `json:"private,omitempty"` } // NetworkSpec for ROSA-HCP. diff --git a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go index d1403b9728..491851a558 100644 --- a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go @@ -184,6 +184,11 @@ func (in *RosaControlPlaneSpec) DeepCopyInto(out *RosaControlPlaneSpec) { (*out)[key] = val } } + if in.Private != nil { + in, out := &in.Private, &out.Private + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaControlPlaneSpec. diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 9bc6594210..68ce6805df 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -281,6 +281,8 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc Tags: rosaScope.ControlPlane.Spec.AdditionalTags, EtcdEncryption: rosaScope.ControlPlane.Spec.EtcdEncryptionKMSArn != "", EtcdEncryptionKMSArn: rosaScope.ControlPlane.Spec.EtcdEncryptionKMSArn, + Private: rosaScope.ControlPlane.Spec.Private, + PrivateLink: rosaScope.ControlPlane.Spec.Private, // all private ROSA HCP clusters are privateLink SubnetIds: rosaScope.ControlPlane.Spec.Subnets, AvailabilityZones: rosaScope.ControlPlane.Spec.AvailabilityZones, From d3048af0b13c156a850ce1888088200408965eae Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Thu, 29 Feb 2024 06:40:27 -0700 Subject: [PATCH 3/4] rosa: expose tag validation error Signed-off-by: Steve Kuznetsov --- .../rosa/controllers/rosacontrolplane_controller.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 68ce6805df..1e6a938ccd 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -209,6 +209,16 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc rosaScope.ControlPlane.Status.FailureMessage = nil } + if errs := rosaScope.ControlPlane.Spec.AdditionalTags.Validate(); errs != nil { + var msg []string + for _, err := range errs { + msg = append(msg, err.Error()) + } + rosaScope.ControlPlane.Status.FailureMessage = ptr.To(strings.Join(msg, ", ")) + // dont' requeue because input is invalid and manual intervention is needed. + return ctrl.Result{}, nil + } + cluster, err := ocmClient.GetCluster(rosaScope.ControlPlane.Spec.RosaClusterName, creator) if weberr.GetType(err) != weberr.NotFound && err != nil { return ctrl.Result{}, err From ce07e081a90bc719a9576a3a13f4f815672db46c Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Thu, 29 Feb 2024 06:54:07 -0700 Subject: [PATCH 4/4] rosa: expose errors in status Signed-off-by: Steve Kuznetsov --- ...ne.cluster.x-k8s.io_rosacontrolplanes.yaml | 8 +- .../api/v1beta2/rosacontrolplane_types.go | 22 ++++- .../rosa/api/v1beta2/zz_generated.deepcopy.go | 5 -- .../rosacontrolplane_controller.go | 88 +++++++++---------- 4 files changed, 63 insertions(+), 60 deletions(-) diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml index dcfa2419c1..cdded84c49 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml @@ -383,7 +383,7 @@ spec: status: properties: conditions: - description: Conditions specifies the cpnditions for the managed control + description: Conditions specifies the conditions for the managed control plane items: description: Condition defines an observation of a Cluster API resource @@ -450,12 +450,12 @@ spec: description: ID is the cluster ID given by ROSA. type: string initialized: - description: Initialized denotes whether or not the control plane - has the uploaded kubernetes config-map. + description: Initialized denotes whether the control plane has the + uploaded kubernetes config-map. type: boolean oidcEndpointURL: description: OIDCEndpointURL is the endpoint url for the managed OIDC - porvider. + provider. type: string ready: default: false diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go index e590254ee8..3c7d8180ae 100644 --- a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go +++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go @@ -118,7 +118,7 @@ type RosaControlPlaneSpec struct { //nolint: maligned // Private restricts master API endpoint and application routes to direct, private connectivity. // Traffic to these endpoints will use AWS PrivateLink to have connectivity between VPCs, AWS services, // and your on-premises networks without exposing your traffic to the public internet. - Private *bool `json:"private,omitempty"` + Private bool `json:"private,omitempty"` } // NetworkSpec for ROSA-HCP. @@ -531,7 +531,7 @@ type RosaControlPlaneStatus struct { // is managed by an external service such as AKS, EKS, GKE, etc. // +kubebuilder:default=true ExternalManagedControlPlane *bool `json:"externalManagedControlPlane,omitempty"` - // Initialized denotes whether or not the control plane has the + // Initialized denotes whether the control plane has the // uploaded kubernetes config-map. // +optional Initialized bool `json:"initialized"` @@ -549,17 +549,31 @@ type RosaControlPlaneStatus struct { // // +optional FailureMessage *string `json:"failureMessage,omitempty"` - // Conditions specifies the cpnditions for the managed control plane + // Conditions specifies the conditions for the managed control plane Conditions clusterv1.Conditions `json:"conditions,omitempty"` // ID is the cluster ID given by ROSA. ID *string `json:"id,omitempty"` // ConsoleURL is the url for the openshift console. ConsoleURL string `json:"consoleURL,omitempty"` - // OIDCEndpointURL is the endpoint url for the managed OIDC porvider. + // OIDCEndpointURL is the endpoint url for the managed OIDC provider. OIDCEndpointURL string `json:"oidcEndpointURL,omitempty"` } +const ( + // OCMCredentialsValidCondition reports whether the OCM credentials are valid. + OCMCredentialsValidCondition clusterv1.ConditionType = "OCMCredentialsValid" + // ROSAControlPlaneConfiguredCondition reports whether the ROSA control plane is configured correctly. + ROSAControlPlaneConfiguredCondition clusterv1.ConditionType = "ROSAControlPlaneConfigured" +) + +const ( + // OCMCredentialsImproperReason denotes that the OCM credentials are not proper. + OCMCredentialsImproperReason = "OCMCredentialsImproper" + // ConfigurationMalformedReason denotes that the configuration provided by the user is malformed. + ConfigurationMalformedReason = "ConfigurationMalformed" +) + // +kubebuilder:object:root=true // +kubebuilder:resource:path=rosacontrolplanes,shortName=rosacp,scope=Namespaced,categories=cluster-api // +kubebuilder:storageversion diff --git a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go index 491851a558..d1403b9728 100644 --- a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go @@ -184,11 +184,6 @@ func (in *RosaControlPlaneSpec) DeepCopyInto(out *RosaControlPlaneSpec) { (*out)[key] = val } } - if in.Private != nil { - in, out := &in.Private, &out.Private - *out = new(bool) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaControlPlaneSpec. diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 1e6a938ccd..7adfdf6e2f 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -177,11 +177,6 @@ func (r *ROSAControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Req func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (res ctrl.Result, reterr error) { rosaScope.Info("Reconciling ROSAControlPlane") - - // if !rosaScope.Cluster.Status.InfrastructureReady { - // rosaScope.Info("Cluster infrastructure is not ready yet") - // return ctrl.Result{RequeueAfter: r.WaitInfraPeriod}, nil - //} if controllerutil.AddFinalizer(rosaScope.ControlPlane, ROSAControlPlaneFinalizer) { if err := rosaScope.PatchObject(); err != nil { return ctrl.Result{}, err @@ -190,8 +185,8 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc ocmClient, err := rosa.NewOCMClient(ctx, rosaScope) if err != nil { - // TODO: need to expose in status, as likely the credentials are invalid - return ctrl.Result{}, fmt.Errorf("failed to create OCM client: %w", err) + conditions.MarkFalse(rosaScope.ControlPlane, rosacontrolplanev1.OCMCredentialsValidCondition, rosacontrolplanev1.OCMCredentialsImproperReason, clusterv1.ConditionSeverityError, err.Error()) + return ctrl.Result{}, nil } creator, err := rosaaws.CreatorForCallerIdentity(rosaScope.Identity) @@ -202,21 +197,36 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc if validationMessage, validationError := validateControlPlaneSpec(ocmClient, rosaScope); validationError != nil { return ctrl.Result{}, fmt.Errorf("validate ROSAControlPlane.spec: %w", validationError) } else if validationMessage != "" { - rosaScope.ControlPlane.Status.FailureMessage = ptr.To(validationMessage) - // dont' requeue because input is invalid and manual intervention is needed. + conditions.MarkFalse(rosaScope.ControlPlane, rosacontrolplanev1.ROSAControlPlaneConfiguredCondition, rosacontrolplanev1.ConfigurationMalformedReason, clusterv1.ConditionSeverityError, validationMessage) return ctrl.Result{}, nil + } + + var machineCIDR, podCIDR, serviceCIDR *net.IPNet + if _, cidr, err := net.ParseCIDR(rosaScope.ControlPlane.Spec.Network.MachineCIDR); err == nil { + machineCIDR = cidr } else { - rosaScope.ControlPlane.Status.FailureMessage = nil + conditions.MarkFalse(rosaScope.ControlPlane, rosacontrolplanev1.ROSAControlPlaneConfiguredCondition, rosacontrolplanev1.ConfigurationMalformedReason, clusterv1.ConditionSeverityError, fmt.Sprintf("rosacontrolplane.spec.network.machineCIDR invalid: %v", err)) + return ctrl.Result{}, nil } - if errs := rosaScope.ControlPlane.Spec.AdditionalTags.Validate(); errs != nil { - var msg []string - for _, err := range errs { - msg = append(msg, err.Error()) + if rosaScope.ControlPlane.Spec.Network.PodCIDR != "" { + _, cidr, err := net.ParseCIDR(rosaScope.ControlPlane.Spec.Network.PodCIDR) + if err == nil { + podCIDR = cidr + } else { + conditions.MarkFalse(rosaScope.ControlPlane, rosacontrolplanev1.ROSAControlPlaneConfiguredCondition, rosacontrolplanev1.ConfigurationMalformedReason, clusterv1.ConditionSeverityError, fmt.Sprintf("rosacontrolplane.spec.network.podCIDR invalid: %v", err)) + return ctrl.Result{}, nil + } + } + + if rosaScope.ControlPlane.Spec.Network.ServiceCIDR != "" { + _, cidr, err := net.ParseCIDR(rosaScope.ControlPlane.Spec.Network.ServiceCIDR) + if err == nil { + serviceCIDR = cidr + } else { + conditions.MarkFalse(rosaScope.ControlPlane, rosacontrolplanev1.ROSAControlPlaneConfiguredCondition, rosacontrolplanev1.ConfigurationMalformedReason, clusterv1.ConditionSeverityError, fmt.Sprintf("rosacontrolplane.spec.network.serviceCIDR invalid: %v", err)) + return ctrl.Result{}, nil } - rosaScope.ControlPlane.Status.FailureMessage = ptr.To(strings.Join(msg, ", ")) - // dont' requeue because input is invalid and manual intervention is needed. - return ctrl.Result{}, nil } cluster, err := ocmClient.GetCluster(rosaScope.ControlPlane.Spec.RosaClusterName, creator) @@ -291,8 +301,8 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc Tags: rosaScope.ControlPlane.Spec.AdditionalTags, EtcdEncryption: rosaScope.ControlPlane.Spec.EtcdEncryptionKMSArn != "", EtcdEncryptionKMSArn: rosaScope.ControlPlane.Spec.EtcdEncryptionKMSArn, - Private: rosaScope.ControlPlane.Spec.Private, - PrivateLink: rosaScope.ControlPlane.Spec.Private, // all private ROSA HCP clusters are privateLink + Private: &rosaScope.ControlPlane.Spec.Private, + PrivateLink: &rosaScope.ControlPlane.Spec.Private, // all private ROSA HCP clusters are privateLink SubnetIds: rosaScope.ControlPlane.Spec.Subnets, AvailabilityZones: rosaScope.ControlPlane.Spec.AvailabilityZones, @@ -310,36 +320,14 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc BillingAccount: billingAccount, AWSCreator: creator, } - - _, machineCIDR, err := net.ParseCIDR(rosaScope.ControlPlane.Spec.Network.MachineCIDR) - if err == nil { + if machineCIDR != nil { spec.MachineCIDR = *machineCIDR - } else { - // TODO: expose in status - rosaScope.Error(err, "rosacontrolplane.spec.network.machineCIDR invalid", rosaScope.ControlPlane.Spec.Network.MachineCIDR) - return ctrl.Result{}, nil } - - if rosaScope.ControlPlane.Spec.Network.PodCIDR != "" { - _, podCIDR, err := net.ParseCIDR(rosaScope.ControlPlane.Spec.Network.PodCIDR) - if err == nil { - spec.PodCIDR = *podCIDR - } else { - // TODO: expose in status. - rosaScope.Error(err, "rosacontrolplane.spec.network.podCIDR invalid", rosaScope.ControlPlane.Spec.Network.PodCIDR) - return ctrl.Result{}, nil - } + if podCIDR != nil { + spec.PodCIDR = *podCIDR } - - if rosaScope.ControlPlane.Spec.Network.ServiceCIDR != "" { - _, serviceCIDR, err := net.ParseCIDR(rosaScope.ControlPlane.Spec.Network.ServiceCIDR) - if err == nil { - spec.ServiceCIDR = *serviceCIDR - } else { - // TODO: expose in status. - rosaScope.Error(err, "rosacontrolplane.spec.network.serviceCIDR invalid", rosaScope.ControlPlane.Spec.Network.ServiceCIDR) - return ctrl.Result{}, nil - } + if serviceCIDR != nil { + spec.ServiceCIDR = *serviceCIDR } // Set autoscale replica @@ -603,7 +591,13 @@ func validateControlPlaneSpec(ocmClient *ocm.Client, rosaScope *scope.ROSAContro return fmt.Sprintf("version %s is not supported", version), nil } - // TODO: add more input validations + if errs := rosaScope.ControlPlane.Spec.AdditionalTags.Validate(); errs != nil { + var msg []string + for _, err := range errs { + msg = append(msg, err.Error()) + } + return strings.Join(msg, ", "), nil + } return "", nil }