Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
1,401 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
237 changes: 237 additions & 0 deletions
237
pkg/controller/operators/operatorcondition_controller.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
package operators | ||
|
||
import ( | ||
"context" | ||
"reflect" | ||
|
||
"github.com/go-logr/logr" | ||
appsv1 "k8s.io/api/apps/v1" | ||
corev1 "k8s.io/api/core/v1" | ||
rbacv1 "k8s.io/api/rbac/v1" | ||
k8serrors "k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/types" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/handler" | ||
"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
"sigs.k8s.io/controller-runtime/pkg/source" | ||
|
||
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" | ||
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" | ||
) | ||
|
||
const ( | ||
OperatorConditionEnvVarKey = "OPERATOR_CONDITION_NAME" | ||
) | ||
|
||
// OperatorReconciler reconciles a Operator object. | ||
type OperatorConditionReconciler struct { | ||
client.Client | ||
log logr.Logger | ||
} | ||
|
||
// +kubebuilder:rbac:groups=operators.coreos.com,resources=operatorconditions,verbs=get;list;update;patch;delete | ||
// +kubebuilder:rbac:groups=operators.coreos.com,resources=operatorconditions/status,verbs=update;patch | ||
|
||
// SetupWithManager adds the OperatorCondition Reconciler reconciler to the given controller manager. | ||
func (r *OperatorConditionReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
handler := &handler.EnqueueRequestForOwner{ | ||
IsController: true, | ||
OwnerType: &operatorsv1.OperatorCondition{}, | ||
} | ||
|
||
return ctrl.NewControllerManagedBy(mgr). | ||
For(&operatorsv1.OperatorCondition{}). | ||
Watches(&source.Kind{Type: &rbacv1.Role{}}, handler). | ||
Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, handler). | ||
Complete(r) | ||
} | ||
|
||
// NewOperatorReconciler constructs and returns an OperatorReconciler. | ||
// As a side effect, the given scheme has operator discovery types added to it. | ||
func NewOperatorConditionReconciler(cli client.Client, log logr.Logger, scheme *runtime.Scheme) (*OperatorConditionReconciler, error) { | ||
// Add watched types to scheme. | ||
if err := AddToScheme(scheme); err != nil { | ||
return nil, err | ||
} | ||
|
||
return &OperatorConditionReconciler{ | ||
Client: cli, | ||
log: log, | ||
}, nil | ||
} | ||
|
||
// Implement reconcile.Reconciler so the controller can reconcile objects | ||
var _ reconcile.Reconciler = &OperatorConditionReconciler{} | ||
|
||
func (r *OperatorConditionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { | ||
// Set up a convenient log object so we don't have to type request over and over again | ||
log := r.log.WithValues("request", req) | ||
log.V(1).Info("reconciling operatorcondition") | ||
|
||
operatorCondition := &operatorsv1.OperatorCondition{} | ||
err := r.Client.Get(context.TODO(), req.NamespacedName, operatorCondition) | ||
if err != nil { | ||
log.V(1).Info("Unable to find operatorcondition", "error", err) | ||
return ctrl.Result{}, err | ||
} | ||
|
||
err = r.ensureOperatorConditionRole(operatorCondition) | ||
if err != nil { | ||
log.V(1).Info("Error reconciling operatorcondition", "error", err) | ||
return ctrl.Result{Requeue: true}, err | ||
} | ||
|
||
err = r.ensureDeploymentEnvVars(operatorCondition) | ||
if err != nil { | ||
log.V(1).Info("Error reconciling operatorcondition", "error", err) | ||
return ctrl.Result{Requeue: true}, err | ||
} | ||
|
||
return ctrl.Result{}, nil | ||
} | ||
|
||
func (r *OperatorConditionReconciler) ensureOperatorConditionRole(operatorCondition *operatorsv1.OperatorCondition) error { | ||
r.log.V(1).Info("Creating the RBAC for the operatorCondition", "operatorConditions", operatorCondition) | ||
subjects := []rbacv1.Subject{} | ||
for _, serviceAccount := range operatorCondition.Spec.ServiceAccounts { | ||
subjects = append(subjects, rbacv1.Subject{ | ||
Kind: rbacv1.ServiceAccountKind, | ||
Name: serviceAccount, | ||
APIGroup: "", | ||
}) | ||
} | ||
|
||
role := &rbacv1.Role{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: operatorCondition.GetName(), | ||
Namespace: operatorCondition.GetNamespace(), | ||
}, | ||
Rules: []rbacv1.PolicyRule{ | ||
{ | ||
Verbs: []string{"get"}, | ||
APIGroups: []string{"operators.coreos.com"}, | ||
Resources: []string{"operatorconditions"}, | ||
ResourceNames: []string{operatorCondition.GetName()}, | ||
}, | ||
{ | ||
Verbs: []string{"get,update,patch"}, | ||
APIGroups: []string{"operators.coreos.com"}, | ||
Resources: []string{"operatorconditions/status"}, | ||
ResourceNames: []string{operatorCondition.GetName()}, | ||
}, | ||
}, | ||
} | ||
ownerutil.AddOwner(role, operatorCondition, false, true) | ||
err := r.Client.Create(context.TODO(), role) | ||
if err != nil { | ||
if !k8serrors.IsAlreadyExists(err) { | ||
return err | ||
} | ||
existingRole := &rbacv1.Role{} | ||
err := r.Client.Get(context.TODO(), client.ObjectKey{Name: role.GetName(), Namespace: role.GetNamespace()}, existingRole) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if ownerutil.IsOwnedBy(existingRole, operatorCondition) && | ||
reflect.DeepEqual(existingRole.Rules, role.Rules) { | ||
r.log.V(1).Info("Existing role does not need to be updated") | ||
return nil | ||
} | ||
|
||
existingRole.OwnerReferences = role.OwnerReferences | ||
existingRole.Rules = role.Rules | ||
err = r.Client.Update(context.TODO(), existingRole) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
roleBinding := &rbacv1.RoleBinding{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: operatorCondition.GetName(), | ||
Namespace: operatorCondition.GetNamespace(), | ||
}, | ||
Subjects: subjects, | ||
RoleRef: rbacv1.RoleRef{ | ||
Kind: "Role", | ||
Name: role.GetName(), | ||
APIGroup: "rbac.authorization.k8s.io", | ||
}, | ||
} | ||
ownerutil.AddOwner(roleBinding, operatorCondition, false, true) | ||
|
||
err = r.Client.Create(context.TODO(), roleBinding) | ||
if err != nil { | ||
if !k8serrors.IsAlreadyExists(err) { | ||
return err | ||
} | ||
existingRoleBinding := &rbacv1.RoleBinding{} | ||
err := r.Client.Get(context.TODO(), client.ObjectKey{Name: roleBinding.GetName(), Namespace: roleBinding.GetNamespace()}, existingRoleBinding) | ||
if err != nil { | ||
return err | ||
} | ||
if ownerutil.IsOwnedBy(existingRoleBinding, operatorCondition) && | ||
existingRoleBinding.RoleRef != roleBinding.RoleRef && | ||
reflect.DeepEqual(existingRoleBinding.Subjects, roleBinding.Subjects) { | ||
r.log.V(1).Info("Existing roleBinding does not need to be updated") | ||
return nil | ||
} | ||
existingRoleBinding.OwnerReferences = roleBinding.OwnerReferences | ||
existingRoleBinding.Subjects = roleBinding.Subjects | ||
existingRoleBinding.RoleRef = roleBinding.RoleRef | ||
err = r.Client.Update(context.TODO(), existingRoleBinding) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (r *OperatorConditionReconciler) ensureDeploymentEnvVars(operatorCondition *operatorsv1.OperatorCondition) error { | ||
for _, deploymentName := range operatorCondition.Spec.Deployments { | ||
deployment := &appsv1.Deployment{} | ||
err := r.Client.Get(context.TODO(), types.NamespacedName{Name: deploymentName, Namespace: operatorCondition.GetNamespace()}, deployment) | ||
if err != nil { | ||
return err | ||
} | ||
deploymentNeedsUpdate := false | ||
for i := range deployment.Spec.Template.Spec.Containers { | ||
envVars, containedEnvVar := containsEnvVar(deployment.Spec.Template.Spec.Containers[i].Env, corev1.EnvVar{Name: OperatorConditionEnvVarKey, Value: operatorCondition.GetName()}) | ||
if !containedEnvVar { | ||
deploymentNeedsUpdate = true | ||
} | ||
deployment.Spec.Template.Spec.Containers[i].Env = envVars | ||
} | ||
if !deploymentNeedsUpdate { | ||
r.log.V(1).Info("Existing deployment does not need to be updated") | ||
continue | ||
} | ||
err = r.Client.Update(context.TODO(), deployment) | ||
if err != nil { | ||
r.log.V(1).Info("error updating deployments", "err", err) | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func containsEnvVar(envVars []corev1.EnvVar, envVar corev1.EnvVar) ([]corev1.EnvVar, bool) { | ||
dupe := make([]corev1.EnvVar, len(envVars)) | ||
copy(dupe, envVars) | ||
for i, each := range dupe { | ||
if each.Name == envVar.Name { | ||
if each.Value == envVar.Value { | ||
return dupe, true | ||
} | ||
dupe[i].Value = envVar.Value | ||
return dupe, false | ||
|
||
} | ||
} | ||
return append(dupe, envVar), false | ||
} |
Oops, something went wrong.