Skip to content

Commit f571cd8

Browse files
committed
Introduce OperatorConditions
This commit introduces two new controllers: The first controller is the OperatorConditionGenerator Controller which reconciles ClusterServiceVersions and creates an OperatorCondtion CR for the CSV. The second controller is the OperatorCondition Controller which reconciles OperatorConditions, Roles and RoleBindings for ServiceAccounts defined in its spec and injecting the OPERATOR_CONDITION_NAME Environment Variable into Deployments defined in its Spec.
1 parent 2b104d9 commit f571cd8

9 files changed

+883
-1
lines changed

cmd/olm/manager.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,32 @@ func Manager(ctx context.Context) (ctrl.Manager, error) {
2323
return nil, err
2424
}
2525

26+
operatorConditionReconciler, err := operators.NewOperatorConditionReconciler(
27+
mgr.GetClient(),
28+
ctrl.Log.WithName("controllers").WithName("operatorcondition"),
29+
mgr.GetScheme(),
30+
)
31+
if err != nil {
32+
return nil, err
33+
}
34+
35+
if err = operatorConditionReconciler.SetupWithManager(mgr); err != nil {
36+
return nil, err
37+
}
38+
39+
operatorConditionGeneratorReconciler, err := operators.NewOperatorConditionGeneratorReconciler(
40+
mgr.GetClient(),
41+
ctrl.Log.WithName("controllers").WithName("operatorcondition-generator"),
42+
mgr.GetScheme(),
43+
)
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
if err = operatorConditionGeneratorReconciler.SetupWithManager(mgr); err != nil {
49+
return nil, err
50+
}
51+
2652
// Setup a new controller to reconcile Operators
2753
setupLog.Info("configuring controller")
2854
if feature.Gate.Enabled(feature.OperatorLifecycleManagerV1) {

pkg/controller/operators/adoption_controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func (r *AdoptionReconciler) SetupWithManager(mgr ctrl.Manager) error {
8282
Watches(&source.Kind{Type: &apiextensionsv1.CustomResourceDefinition{}}, enqueueProviders).
8383
Watches(&source.Kind{Type: &apiregistrationv1.APIService{}}, enqueueCSV).
8484
Watches(&source.Kind{Type: &operatorsv1alpha1.Subscription{}}, enqueueCSV).
85+
Watches(&source.Kind{Type: &operatorsv1.OperatorCondition{}}, enqueueCSV).
8586
Complete(reconcile.Func(r.ReconcileClusterServiceVersion))
8687
if err != nil {
8788
return err
@@ -324,6 +325,7 @@ func (r *AdoptionReconciler) adoptees(ctx context.Context, operator decorators.O
324325
&operatorsv1alpha1.SubscriptionList{},
325326
&operatorsv1alpha1.InstallPlanList{},
326327
&operatorsv1alpha1.ClusterServiceVersionList{},
328+
&operatorsv1.OperatorConditionList{},
327329
}
328330

329331
// Only resources that aren't already labelled are adoption candidates

pkg/controller/operators/operator_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ var (
3333
apiregistrationv1.AddToScheme,
3434
operatorsv1alpha1.AddToScheme,
3535
operatorsv1.AddToScheme,
36-
operatorsv1.AddToScheme,
3736
)
3837

3938
// AddToScheme adds all types necessary for the controller to operate.
@@ -81,6 +80,7 @@ func (r *OperatorReconciler) SetupWithManager(mgr ctrl.Manager) error {
8180
Watches(&source.Kind{Type: &operatorsv1alpha1.Subscription{}}, enqueueOperator).
8281
Watches(&source.Kind{Type: &operatorsv1alpha1.InstallPlan{}}, enqueueOperator).
8382
Watches(&source.Kind{Type: &operatorsv1alpha1.ClusterServiceVersion{}}, enqueueOperator).
83+
Watches(&source.Kind{Type: &operatorsv1.OperatorCondition{}}, enqueueOperator).
8484
// TODO(njhale): Add WebhookConfigurations and ConfigMaps
8585
Complete(r)
8686
}
@@ -205,6 +205,7 @@ func (r *OperatorReconciler) listComponents(ctx context.Context, selector labels
205205
&operatorsv1alpha1.SubscriptionList{},
206206
&operatorsv1alpha1.InstallPlanList{},
207207
&operatorsv1alpha1.ClusterServiceVersionList{},
208+
&operatorsv1.OperatorConditionList{},
208209
}
209210

210211
opt := client.MatchingLabelsSelector{Selector: selector}
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
package operators
2+
3+
import (
4+
"context"
5+
"reflect"
6+
7+
"github.com/go-logr/logr"
8+
appsv1 "k8s.io/api/apps/v1"
9+
corev1 "k8s.io/api/core/v1"
10+
rbacv1 "k8s.io/api/rbac/v1"
11+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/runtime"
14+
"k8s.io/apimachinery/pkg/types"
15+
ctrl "sigs.k8s.io/controller-runtime"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
"sigs.k8s.io/controller-runtime/pkg/handler"
18+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
19+
"sigs.k8s.io/controller-runtime/pkg/source"
20+
21+
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
22+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
23+
)
24+
25+
const (
26+
OperatorConditionEnvVarKey = "OPERATOR_CONDITION_NAME"
27+
)
28+
29+
// OperatorConditionReconciler reconciles an OperatorCondition object.
30+
type OperatorConditionReconciler struct {
31+
client.Client
32+
log logr.Logger
33+
}
34+
35+
// +kubebuilder:rbac:groups=operators.coreos.com,resources=operatorconditions,verbs=get;list;update;patch;delete
36+
// +kubebuilder:rbac:groups=operators.coreos.com,resources=operatorconditions/status,verbs=update;patch
37+
38+
// SetupWithManager adds the OperatorCondition Reconciler reconciler to the given controller manager.
39+
func (r *OperatorConditionReconciler) SetupWithManager(mgr ctrl.Manager) error {
40+
handler := &handler.EnqueueRequestForOwner{
41+
IsController: true,
42+
OwnerType: &operatorsv1.OperatorCondition{},
43+
}
44+
45+
return ctrl.NewControllerManagedBy(mgr).
46+
For(&operatorsv1.OperatorCondition{}).
47+
Watches(&source.Kind{Type: &rbacv1.Role{}}, handler).
48+
Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, handler).
49+
Complete(r)
50+
}
51+
52+
// NewOperatorConditionReconciler constructs and returns an OperatorConditionReconciler.
53+
// As a side effect, the given scheme has operator discovery types added to it.
54+
func NewOperatorConditionReconciler(cli client.Client, log logr.Logger, scheme *runtime.Scheme) (*OperatorConditionReconciler, error) {
55+
// Add watched types to scheme.
56+
if err := AddToScheme(scheme); err != nil {
57+
return nil, err
58+
}
59+
60+
return &OperatorConditionReconciler{
61+
Client: cli,
62+
log: log,
63+
}, nil
64+
}
65+
66+
// Implement reconcile.Reconciler so the controller can reconcile objects
67+
var _ reconcile.Reconciler = &OperatorConditionReconciler{}
68+
69+
func (r *OperatorConditionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
70+
// Set up a convenient log object so we don't have to type request over and over again
71+
log := r.log.WithValues("request", req)
72+
log.V(2).Info("reconciling operatorcondition")
73+
74+
operatorCondition := &operatorsv1.OperatorCondition{}
75+
err := r.Client.Get(context.TODO(), req.NamespacedName, operatorCondition)
76+
if err != nil {
77+
log.V(1).Error(err, "Unable to find operatorcondition")
78+
return ctrl.Result{}, err
79+
}
80+
81+
err = r.ensureOperatorConditionRole(operatorCondition)
82+
if err != nil {
83+
log.V(1).Error(err, "Error ensuring OperatorCondition Role")
84+
return ctrl.Result{Requeue: true}, err
85+
}
86+
87+
err = r.ensureOperatorConditionRoleBinding(operatorCondition)
88+
if err != nil {
89+
log.V(1).Error(err, "Error ensuring OperatorCondition RoleBinding")
90+
return ctrl.Result{Requeue: true}, err
91+
}
92+
93+
err = r.ensureDeploymentEnvVars(operatorCondition)
94+
if err != nil {
95+
log.V(1).Error(err, "Error ensuring OperatorCondition Deployment EnvVars")
96+
return ctrl.Result{Requeue: true}, err
97+
}
98+
99+
return ctrl.Result{}, nil
100+
}
101+
102+
func (r *OperatorConditionReconciler) ensureOperatorConditionRole(operatorCondition *operatorsv1.OperatorCondition) error {
103+
r.log.V(4).Info("Ensuring the Role for the OperatorCondition")
104+
role := &rbacv1.Role{
105+
ObjectMeta: metav1.ObjectMeta{
106+
Name: operatorCondition.GetName(),
107+
Namespace: operatorCondition.GetNamespace(),
108+
},
109+
Rules: []rbacv1.PolicyRule{
110+
{
111+
Verbs: []string{"get"},
112+
APIGroups: []string{"operators.coreos.com"},
113+
Resources: []string{"operatorconditions"},
114+
ResourceNames: []string{operatorCondition.GetName()},
115+
},
116+
{
117+
Verbs: []string{"get,update,patch"},
118+
APIGroups: []string{"operators.coreos.com"},
119+
Resources: []string{"operatorconditions/status"},
120+
ResourceNames: []string{operatorCondition.GetName()},
121+
},
122+
},
123+
}
124+
ownerutil.AddOwner(role, operatorCondition, false, true)
125+
126+
existingRole := &rbacv1.Role{}
127+
err := r.Client.Get(context.TODO(), client.ObjectKey{Name: role.GetName(), Namespace: role.GetNamespace()}, existingRole)
128+
if err != nil {
129+
if !k8serrors.IsNotFound(err) {
130+
return err
131+
}
132+
return r.Client.Create(context.TODO(), role)
133+
}
134+
135+
if ownerutil.IsOwnedBy(existingRole, operatorCondition) &&
136+
reflect.DeepEqual(role.Rules, existingRole.Rules) {
137+
r.log.V(5).Info("Existing Role does not need to be updated")
138+
return nil
139+
}
140+
r.log.V(5).Info("Existing Role needs to be updated")
141+
142+
existingRole.OwnerReferences = role.OwnerReferences
143+
existingRole.Rules = role.Rules
144+
return r.Client.Update(context.TODO(), existingRole)
145+
}
146+
147+
func (r *OperatorConditionReconciler) ensureOperatorConditionRoleBinding(operatorCondition *operatorsv1.OperatorCondition) error {
148+
r.log.V(4).Info("Ensuring the RoleBinding for the OperatorCondition")
149+
subjects := []rbacv1.Subject{}
150+
for _, serviceAccount := range operatorCondition.Spec.ServiceAccounts {
151+
subjects = append(subjects, rbacv1.Subject{
152+
Kind: rbacv1.ServiceAccountKind,
153+
Name: serviceAccount,
154+
APIGroup: "",
155+
})
156+
}
157+
158+
roleBinding := &rbacv1.RoleBinding{
159+
ObjectMeta: metav1.ObjectMeta{
160+
Name: operatorCondition.GetName(),
161+
Namespace: operatorCondition.GetNamespace(),
162+
},
163+
Subjects: subjects,
164+
RoleRef: rbacv1.RoleRef{
165+
Kind: "Role",
166+
Name: operatorCondition.GetName(),
167+
APIGroup: "rbac.authorization.k8s.io",
168+
},
169+
}
170+
ownerutil.AddOwner(roleBinding, operatorCondition, false, true)
171+
172+
existingRoleBinding := &rbacv1.RoleBinding{}
173+
err := r.Client.Get(context.TODO(), client.ObjectKey{Name: roleBinding.GetName(), Namespace: roleBinding.GetNamespace()}, existingRoleBinding)
174+
if err != nil {
175+
if !k8serrors.IsNotFound(err) {
176+
return err
177+
}
178+
return r.Client.Create(context.TODO(), roleBinding)
179+
}
180+
181+
if ownerutil.IsOwnedBy(existingRoleBinding, operatorCondition) &&
182+
existingRoleBinding.RoleRef == roleBinding.RoleRef &&
183+
reflect.DeepEqual(roleBinding.Subjects, existingRoleBinding.Subjects) {
184+
r.log.V(5).Info("Existing RoleBinding does not need to be updated")
185+
return nil
186+
}
187+
188+
r.log.V(5).Info("Existing RoleBinding needs to be updated")
189+
existingRoleBinding.OwnerReferences = roleBinding.OwnerReferences
190+
existingRoleBinding.Subjects = roleBinding.Subjects
191+
existingRoleBinding.RoleRef = roleBinding.RoleRef
192+
193+
return r.Client.Update(context.TODO(), existingRoleBinding)
194+
}
195+
196+
func (r *OperatorConditionReconciler) ensureDeploymentEnvVars(operatorCondition *operatorsv1.OperatorCondition) error {
197+
r.log.V(4).Info("Ensuring that deployments have the OPERATOR_CONDITION_NAME variable")
198+
for _, deploymentName := range operatorCondition.Spec.Deployments {
199+
deployment := &appsv1.Deployment{}
200+
err := r.Client.Get(context.TODO(), types.NamespacedName{Name: deploymentName, Namespace: operatorCondition.GetNamespace()}, deployment)
201+
if err != nil {
202+
return err
203+
}
204+
deploymentNeedsUpdate := false
205+
for i := range deployment.Spec.Template.Spec.Containers {
206+
envVars, containedEnvVar := ensureEnvVarIsPresent(deployment.Spec.Template.Spec.Containers[i].Env, corev1.EnvVar{Name: OperatorConditionEnvVarKey, Value: operatorCondition.GetName()})
207+
if !containedEnvVar {
208+
deploymentNeedsUpdate = true
209+
}
210+
deployment.Spec.Template.Spec.Containers[i].Env = envVars
211+
}
212+
if !deploymentNeedsUpdate {
213+
r.log.V(5).Info("Existing deployment does not need to be updated")
214+
continue
215+
}
216+
r.log.V(5).Info("Existing deployment needs to be updated")
217+
err = r.Client.Update(context.TODO(), deployment)
218+
if err != nil {
219+
return err
220+
}
221+
}
222+
return nil
223+
}
224+
225+
func ensureEnvVarIsPresent(envVars []corev1.EnvVar, envVar corev1.EnvVar) ([]corev1.EnvVar, bool) {
226+
for i, each := range envVars {
227+
if each.Name == envVar.Name {
228+
if each.Value == envVar.Value {
229+
return envVars, true
230+
}
231+
envVars[i].Value = envVar.Value
232+
return envVars, false
233+
}
234+
}
235+
return append(envVars, envVar), false
236+
}

0 commit comments

Comments
 (0)