|
| 1 | +/* |
| 2 | +Copyright 2025 IBM Corporation |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
| 17 | +package bootstrap |
| 18 | + |
| 19 | +import ( |
| 20 | + "context" |
| 21 | + "fmt" |
| 22 | + "strconv" |
| 23 | + |
| 24 | + operatorv1alpha1 "github.com/IBM/ibm-iam-operator/apis/operator/v1alpha1" |
| 25 | + ctrlcommon "github.com/IBM/ibm-iam-operator/controllers/common" |
| 26 | + "github.com/IBM/ibm-iam-operator/version" |
| 27 | + "github.com/opdev/subreconciler" |
| 28 | + corev1 "k8s.io/api/core/v1" |
| 29 | + k8sErrors "k8s.io/apimachinery/pkg/api/errors" |
| 30 | + "k8s.io/apimachinery/pkg/types" |
| 31 | + ctrl "sigs.k8s.io/controller-runtime" |
| 32 | + "sigs.k8s.io/controller-runtime/pkg/builder" |
| 33 | + "sigs.k8s.io/controller-runtime/pkg/client" |
| 34 | + "sigs.k8s.io/controller-runtime/pkg/handler" |
| 35 | + logf "sigs.k8s.io/controller-runtime/pkg/log" |
| 36 | + "sigs.k8s.io/controller-runtime/pkg/predicate" |
| 37 | +) |
| 38 | + |
| 39 | +// BootStrapReconciler handles modifications to the Authentication CR before it is reconciled by the |
| 40 | +// main Authentication controller |
| 41 | +type BootstrapReconciler struct { |
| 42 | + client.Client |
| 43 | +} |
| 44 | + |
| 45 | +func (r *BootstrapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { |
| 46 | + log := logf.FromContext(ctx).WithName("controller_authentication_bootstrap") |
| 47 | + subCtx := logf.IntoContext(ctx, log) |
| 48 | + if subResult, err := r.makeAuthenticationCorrections(subCtx, req); subreconciler.ShouldHaltOrRequeue(subResult, err) { |
| 49 | + if err != nil { |
| 50 | + log.Error(err, "An error was encountered during Authentication bootstrap") |
| 51 | + } else { |
| 52 | + log.Info("Needed to requeue during Authentication bootstrap") |
| 53 | + } |
| 54 | + return subreconciler.Evaluate(subResult, err) |
| 55 | + } |
| 56 | + log.Info("Authentication bootstrap successful") |
| 57 | + return |
| 58 | +} |
| 59 | + |
| 60 | +// SetupWithManager sets up the controller with the Manager. |
| 61 | +func (r *BootstrapReconciler) SetupWithManager(mgr ctrl.Manager) error { |
| 62 | + |
| 63 | + authCtrl := ctrl.NewControllerManagedBy(mgr) |
| 64 | + |
| 65 | + bootstrapPred := predicate.NewPredicateFuncs(func(o client.Object) bool { |
| 66 | + return o.GetLabels()[ctrlcommon.ManagerVersionLabel] != version.Version |
| 67 | + }) |
| 68 | + |
| 69 | + authCtrl.Watches(&operatorv1alpha1.Authentication{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(bootstrapPred)) |
| 70 | + return authCtrl.Named("controller_authentication_bootstrap").Complete(r) |
| 71 | +} |
| 72 | + |
| 73 | +// makeAuthenticationCorrections handles changes that need to happen to the Authentication CR for compatibility reasons. |
| 74 | +func (r *BootstrapReconciler) makeAuthenticationCorrections(ctx context.Context, req ctrl.Request) (result *ctrl.Result, err error) { |
| 75 | + log := logf.FromContext(ctx) |
| 76 | + authCR := &operatorv1alpha1.Authentication{} |
| 77 | + if result, err = r.getLatestAuthentication(ctx, req, authCR); subreconciler.ShouldHaltOrRequeue(result, err) { |
| 78 | + return |
| 79 | + } |
| 80 | + if authCR.Labels[ctrlcommon.ManagerVersionLabel] == version.Version { |
| 81 | + return subreconciler.ContinueReconciling() |
| 82 | + } |
| 83 | + |
| 84 | + if needsAuditServiceDummyDataReset(authCR) { |
| 85 | + authCR.SetRequiredDummyData() |
| 86 | + } |
| 87 | + |
| 88 | + if err = r.writeConfigurationsToAuthenticationCR(ctx, authCR); err != nil { |
| 89 | + log.Error(err, "Failed to update the Authentication") |
| 90 | + return subreconciler.RequeueWithError(err) |
| 91 | + } |
| 92 | + |
| 93 | + authCR.Labels[ctrlcommon.ManagerVersionLabel] = version.Version |
| 94 | + |
| 95 | + err = r.Update(ctx, authCR) |
| 96 | + if err != nil { |
| 97 | + log.Error(err, "Failed to update the Authentication") |
| 98 | + return subreconciler.RequeueWithError(err) |
| 99 | + } |
| 100 | + log.Info("Performed bootstrap update to Authentication successfully") |
| 101 | + |
| 102 | + return subreconciler.ContinueReconciling() |
| 103 | +} |
| 104 | + |
| 105 | +// writeConfigurationsToAuthenticationCR copies values from the |
| 106 | +// platform-auth-idp ConfigMap to the Authentication CR. |
| 107 | +func (r *BootstrapReconciler) writeConfigurationsToAuthenticationCR(ctx context.Context, authCR *operatorv1alpha1.Authentication) (err error) { |
| 108 | + platformAuthIDPCM := &corev1.ConfigMap{} |
| 109 | + if err = r.Get(ctx, types.NamespacedName{Name: "platform-auth-idp", Namespace: authCR.Namespace}, platformAuthIDPCM); k8sErrors.IsNotFound(err) { |
| 110 | + return nil |
| 111 | + } else if err != nil { |
| 112 | + return fmt.Errorf("failed to get ConfigMap: %w", err) |
| 113 | + } |
| 114 | + keys := map[string]any{ |
| 115 | + "ROKS_URL": &authCR.Spec.Config.ROKSURL, |
| 116 | + "ROKS_USER_PREFIX": &authCR.Spec.Config.ROKSUserPrefix, |
| 117 | + "ROKS_ENABLED": &authCR.Spec.Config.ROKSEnabled, |
| 118 | + "BOOTSTRAP_USERID": &authCR.Spec.Config.BootstrapUserId, |
| 119 | + "CLAIMS_SUPPORTED": &authCR.Spec.Config.ClaimsSupported, |
| 120 | + "CLAIMS_MAP": &authCR.Spec.Config.ClaimsMap, |
| 121 | + "DEFAULT_LOGIN": &authCR.Spec.Config.DefaultLogin, |
| 122 | + "SCOPE_CLAIM": &authCR.Spec.Config.ScopeClaim, |
| 123 | + "NONCE_ENABLED": &authCR.Spec.Config.NONCEEnabled, |
| 124 | + "PREFERRED_LOGIN": &authCR.Spec.Config.PreferredLogin, |
| 125 | + "OIDC_ISSUER_URL": &authCR.Spec.Config.OIDCIssuerURL, |
| 126 | + "PROVIDER_ISSUER_URL": &authCR.Spec.Config.ProviderIssuerURL, |
| 127 | + "CLUSTER_NAME": &authCR.Spec.Config.ClusterName, |
| 128 | + "FIPS_ENABLED": &authCR.Spec.Config.FIPSEnabled, |
| 129 | + "IBM_CLOUD_SAAS": &authCR.Spec.Config.IBMCloudSaas, |
| 130 | + "SAAS_CLIENT_REDIRECT_URL": &authCR.Spec.Config.SaasClientRedirectUrl, |
| 131 | + "ATTR_MAPPING_FROM_CONFIG": &authCR.Spec.Config.AttrMappingFromConfig, |
| 132 | + } |
| 133 | + |
| 134 | + for key, crField := range keys { |
| 135 | + cmValue, ok := platformAuthIDPCM.Data[key] |
| 136 | + if !ok { |
| 137 | + continue |
| 138 | + } |
| 139 | + switch crValue := crField.(type) { |
| 140 | + case *string: |
| 141 | + if *crValue != cmValue { |
| 142 | + *crValue = cmValue |
| 143 | + } |
| 144 | + case *bool: |
| 145 | + cmValueBool, _ := strconv.ParseBool(cmValue) |
| 146 | + if *crValue != cmValueBool { |
| 147 | + *crValue = cmValueBool |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + return |
| 153 | +} |
| 154 | + |
| 155 | +// needsAuditServiceDummyDataReset compares the state in an Authentication's .spec.auditService and returns whether it |
| 156 | +// needs to be overwritten with dummy data. |
| 157 | +func needsAuditServiceDummyDataReset(a *operatorv1alpha1.Authentication) bool { |
| 158 | + return a.Spec.AuditService.ImageName != operatorv1alpha1.AuditServiceIgnoreString || |
| 159 | + a.Spec.AuditService.ImageRegistry != operatorv1alpha1.AuditServiceIgnoreString || |
| 160 | + a.Spec.AuditService.ImageTag != operatorv1alpha1.AuditServiceIgnoreString || |
| 161 | + a.Spec.AuditService.SyslogTlsPath != "" || |
| 162 | + a.Spec.AuditService.Resources != nil |
| 163 | +} |
| 164 | + |
| 165 | +func (r *BootstrapReconciler) getLatestAuthentication(ctx context.Context, req ctrl.Request, authentication *operatorv1alpha1.Authentication) (result *ctrl.Result, err error) { |
| 166 | + reqLogger := logf.FromContext(ctx) |
| 167 | + if err := r.Get(ctx, req.NamespacedName, authentication); err != nil { |
| 168 | + if k8sErrors.IsNotFound(err) { |
| 169 | + reqLogger.Info("Authentication not found; skipping reconciliation") |
| 170 | + return subreconciler.DoNotRequeue() |
| 171 | + } |
| 172 | + reqLogger.Error(err, "Failed to get Authentication") |
| 173 | + return subreconciler.RequeueWithError(err) |
| 174 | + } |
| 175 | + return subreconciler.ContinueReconciling() |
| 176 | +} |
0 commit comments