github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/operatorcondition_controller.go (about) 1 package operators 2 3 import ( 4 "context" 5 "reflect" 6 7 "github.com/go-logr/logr" 8 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" 9 appsv1 "k8s.io/api/apps/v1" 10 corev1 "k8s.io/api/core/v1" 11 rbacv1 "k8s.io/api/rbac/v1" 12 apierrors "k8s.io/apimachinery/pkg/api/errors" 13 "k8s.io/apimachinery/pkg/api/meta" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/runtime" 16 "k8s.io/apimachinery/pkg/types" 17 ctrl "sigs.k8s.io/controller-runtime" 18 "sigs.k8s.io/controller-runtime/pkg/client" 19 "sigs.k8s.io/controller-runtime/pkg/handler" 20 "sigs.k8s.io/controller-runtime/pkg/reconcile" 21 22 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 23 operatorsv2 "github.com/operator-framework/api/pkg/operators/v2" 24 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 25 "github.com/operator-framework/operator-lifecycle-manager/pkg/metrics" 26 ) 27 28 const ( 29 OperatorConditionEnvVarKey = "OPERATOR_CONDITION_NAME" 30 ) 31 32 // OperatorConditionReconciler reconciles an OperatorCondition object. 33 type OperatorConditionReconciler struct { 34 client.Client 35 log logr.Logger 36 } 37 38 // +kubebuilder:rbac:groups=operators.coreos.com,resources=operatorconditions,verbs=get;list;update;patch;delete 39 // +kubebuilder:rbac:groups=operators.coreos.com,resources=operatorconditions/status,verbs=update;patch 40 41 // SetupWithManager adds the OperatorCondition Reconciler reconciler to the given controller manager. 42 func (r *OperatorConditionReconciler) SetupWithManager(mgr ctrl.Manager) error { 43 deploymentHandler := handler.EnqueueRequestsFromMapFunc(r.mapToOperatorCondition) 44 handler := handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &operatorsv2.OperatorCondition{}, handler.OnlyControllerOwner()) 45 46 return ctrl.NewControllerManagedBy(mgr). 47 For(&operatorsv2.OperatorCondition{}). 48 Watches(&rbacv1.Role{}, handler). 49 Watches(&rbacv1.RoleBinding{}, handler). 50 Watches(&appsv1.Deployment{}, deploymentHandler). 51 Complete(r) 52 } 53 54 func (r *OperatorConditionReconciler) mapToOperatorCondition(_ context.Context, obj client.Object) (requests []reconcile.Request) { 55 if obj == nil { 56 return nil 57 } 58 59 owner := ownerutil.GetOwnerByKind(obj, operatorsv1alpha1.ClusterServiceVersionKind) 60 if owner == nil { 61 return nil 62 } 63 64 return []reconcile.Request{ 65 { 66 NamespacedName: types.NamespacedName{ 67 Name: owner.Name, 68 Namespace: obj.GetNamespace(), 69 }, 70 }, 71 } 72 } 73 74 // NewOperatorConditionReconciler constructs and returns an OperatorConditionReconciler. 75 // As a side effect, the given scheme has operator discovery types added to it. 76 func NewOperatorConditionReconciler(cli client.Client, log logr.Logger, scheme *runtime.Scheme) (*OperatorConditionReconciler, error) { 77 // Add watched types to scheme. 78 if err := AddToScheme(scheme); err != nil { 79 return nil, err 80 } 81 82 return &OperatorConditionReconciler{ 83 Client: cli, 84 log: log, 85 }, nil 86 } 87 88 // Implement reconcile.Reconciler so the controller can reconcile objects 89 var _ reconcile.Reconciler = &OperatorConditionReconciler{} 90 91 func (r *OperatorConditionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 92 // Set up a convenient log object so we don't have to type request over and over again 93 log := r.log.WithValues("request", req).V(1) 94 log.Info("reconciling") 95 metrics.EmitOperatorConditionReconcile(req.Namespace, req.Name) 96 97 operatorCondition := &operatorsv2.OperatorCondition{} 98 if err := r.Client.Get(ctx, req.NamespacedName, operatorCondition); err != nil { 99 log.Info("Unable to find OperatorCondition") 100 return ctrl.Result{}, client.IgnoreNotFound(err) 101 } 102 103 if err := r.ensureOperatorConditionRole(operatorCondition); err != nil { 104 log.Info("Error ensuring OperatorCondition Role") 105 return ctrl.Result{}, err 106 } 107 108 if err := r.ensureOperatorConditionRoleBinding(operatorCondition); err != nil { 109 log.Info("Error ensuring OperatorCondition RoleBinding") 110 return ctrl.Result{}, err 111 } 112 113 if err := r.ensureDeploymentEnvVars(operatorCondition); err != nil { 114 log.Info("Error ensuring OperatorCondition Deployment EnvVars") 115 return ctrl.Result{}, err 116 } 117 118 if err := r.syncOperatorConditionStatus(operatorCondition); err != nil { 119 log.Info("Error syncing OperatorCondition Status") 120 return ctrl.Result{}, err 121 } 122 123 return ctrl.Result{}, nil 124 } 125 126 func (r *OperatorConditionReconciler) ensureOperatorConditionRole(operatorCondition *operatorsv2.OperatorCondition) error { 127 r.log.V(4).Info("Ensuring the Role for the OperatorCondition") 128 role := &rbacv1.Role{ 129 ObjectMeta: metav1.ObjectMeta{ 130 Name: operatorCondition.GetName(), 131 Namespace: operatorCondition.GetNamespace(), 132 Labels: map[string]string{ 133 install.OLMManagedLabelKey: install.OLMManagedLabelValue, 134 }, 135 }, 136 Rules: []rbacv1.PolicyRule{ 137 { 138 Verbs: []string{"get", "update", "patch"}, 139 APIGroups: []string{"operators.coreos.com"}, 140 Resources: []string{"operatorconditions"}, 141 ResourceNames: []string{operatorCondition.GetName()}, 142 }, 143 }, 144 } 145 ownerutil.AddOwner(role, operatorCondition, false, true) 146 147 existingRole := &rbacv1.Role{} 148 err := r.Client.Get(context.TODO(), client.ObjectKey{Name: role.GetName(), Namespace: role.GetNamespace()}, existingRole) 149 if err != nil { 150 if !apierrors.IsNotFound(err) { 151 return err 152 } 153 err = r.Client.Create(context.TODO(), role) 154 if apierrors.IsAlreadyExists(err) { 155 return r.Client.Update(context.TODO(), role) 156 } 157 } 158 159 if ownerutil.IsOwnedBy(existingRole, operatorCondition) && 160 reflect.DeepEqual(role.Rules, existingRole.Rules) { 161 r.log.V(5).Info("Existing Role does not need to be updated") 162 return nil 163 } 164 r.log.V(5).Info("Existing Role needs to be updated") 165 166 existingRole.OwnerReferences = role.OwnerReferences 167 existingRole.Rules = role.Rules 168 return r.Client.Update(context.TODO(), existingRole) 169 } 170 171 func (r *OperatorConditionReconciler) ensureOperatorConditionRoleBinding(operatorCondition *operatorsv2.OperatorCondition) error { 172 r.log.V(4).Info("Ensuring the RoleBinding for the OperatorCondition") 173 subjects := []rbacv1.Subject{} 174 for _, serviceAccount := range operatorCondition.Spec.ServiceAccounts { 175 subjects = append(subjects, rbacv1.Subject{ 176 Kind: rbacv1.ServiceAccountKind, 177 Name: serviceAccount, 178 APIGroup: "", 179 }) 180 } 181 182 roleBinding := &rbacv1.RoleBinding{ 183 ObjectMeta: metav1.ObjectMeta{ 184 Name: operatorCondition.GetName(), 185 Namespace: operatorCondition.GetNamespace(), 186 Labels: map[string]string{ 187 install.OLMManagedLabelKey: install.OLMManagedLabelValue, 188 }, 189 }, 190 Subjects: subjects, 191 RoleRef: rbacv1.RoleRef{ 192 Kind: "Role", 193 Name: operatorCondition.GetName(), 194 APIGroup: "rbac.authorization.k8s.io", 195 }, 196 } 197 ownerutil.AddOwner(roleBinding, operatorCondition, false, true) 198 199 existingRoleBinding := &rbacv1.RoleBinding{} 200 err := r.Client.Get(context.TODO(), client.ObjectKey{Name: roleBinding.GetName(), Namespace: roleBinding.GetNamespace()}, existingRoleBinding) 201 if err != nil { 202 if !apierrors.IsNotFound(err) { 203 return err 204 } 205 err = r.Client.Create(context.TODO(), roleBinding) 206 if apierrors.IsAlreadyExists(err) { 207 return r.Client.Update(context.TODO(), roleBinding) 208 } 209 } 210 211 if ownerutil.IsOwnedBy(existingRoleBinding, operatorCondition) && 212 existingRoleBinding.RoleRef == roleBinding.RoleRef && 213 reflect.DeepEqual(roleBinding.Subjects, existingRoleBinding.Subjects) { 214 r.log.V(5).Info("Existing RoleBinding does not need to be updated") 215 return nil 216 } 217 218 r.log.V(5).Info("Existing RoleBinding needs to be updated") 219 existingRoleBinding.OwnerReferences = roleBinding.OwnerReferences 220 existingRoleBinding.Subjects = roleBinding.Subjects 221 existingRoleBinding.RoleRef = roleBinding.RoleRef 222 223 return r.Client.Update(context.TODO(), existingRoleBinding) 224 } 225 226 func (r *OperatorConditionReconciler) ensureDeploymentEnvVars(operatorCondition *operatorsv2.OperatorCondition) error { 227 r.log.V(4).Info("Ensuring that deployments have the OPERATOR_CONDITION_NAME variable") 228 for _, deploymentName := range operatorCondition.Spec.Deployments { 229 deployment := &appsv1.Deployment{} 230 err := r.Client.Get(context.TODO(), types.NamespacedName{Name: deploymentName, Namespace: operatorCondition.GetNamespace()}, deployment) 231 if err != nil { 232 return err 233 } 234 235 // Check the deployment is owned by a CSV with the same name as the OperatorCondition. 236 deploymentOwner := ownerutil.GetOwnerByKind(deployment, operatorsv1alpha1.ClusterServiceVersionKind) 237 if deploymentOwner == nil || deploymentOwner.Name != operatorCondition.GetName() { 238 continue 239 } 240 241 deploymentNeedsUpdate := false 242 for i := range deployment.Spec.Template.Spec.Containers { 243 envVars, containedEnvVar := ensureEnvVarIsPresent(deployment.Spec.Template.Spec.Containers[i].Env, corev1.EnvVar{Name: OperatorConditionEnvVarKey, Value: operatorCondition.GetName()}) 244 if !containedEnvVar { 245 deploymentNeedsUpdate = true 246 } 247 deployment.Spec.Template.Spec.Containers[i].Env = envVars 248 } 249 if !deploymentNeedsUpdate { 250 r.log.V(5).Info("Existing deployment does not need to be updated") 251 continue 252 } 253 r.log.V(5).Info("Existing deployment needs to be updated") 254 err = r.Client.Update(context.TODO(), deployment) 255 if err != nil { 256 return err 257 } 258 } 259 return nil 260 } 261 262 func (r *OperatorConditionReconciler) syncOperatorConditionStatus(operatorCondition *operatorsv2.OperatorCondition) error { 263 r.log.V(4).Info("Sync operatorcondition status") 264 currentGen := operatorCondition.ObjectMeta.GetGeneration() 265 changed := false 266 for _, cond := range operatorCondition.Spec.Conditions { 267 if c := meta.FindStatusCondition(operatorCondition.Status.Conditions, cond.Type); c != nil { 268 if cond.Status == c.Status && c.ObservedGeneration == currentGen { 269 continue 270 } 271 } 272 cond.ObservedGeneration = currentGen 273 meta.SetStatusCondition(&operatorCondition.Status.Conditions, cond) 274 changed = true 275 } 276 277 if changed { 278 return r.Client.Status().Update(context.TODO(), operatorCondition) 279 } 280 return nil 281 } 282 283 func ensureEnvVarIsPresent(envVars []corev1.EnvVar, envVar corev1.EnvVar) ([]corev1.EnvVar, bool) { 284 for i, each := range envVars { 285 if each.Name == envVar.Name { 286 if each.Value == envVar.Value { 287 return envVars, true 288 } 289 envVars[i].Value = envVar.Value 290 return envVars, false 291 } 292 } 293 return append(envVars, envVar), false 294 }