Skip to content

Commit 1ff6eff

Browse files
committed
(feat) use scoped client for plan execution
For plan execution, use scoped client that is bound to the service account specified in the operator group. - When operator group is synced, check if a service account has been specified. If so, make sure it exists and update status accordingly. - Refactor 'ExecutePlan' - move management of all step resource(s) except for CRD into a new layer 'StepEnsurer' - Add a new strategy that provides appropriately scoped client(s) to be used for an operator that is being installed.
1 parent 5a4444c commit 1ff6eff

File tree

11 files changed

+754
-165
lines changed

11 files changed

+754
-165
lines changed

pkg/api/apis/operators/v1/operatorgroup_types.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,21 @@ func (o *OperatorGroup) BuildTargetNamespaces() string {
7676
sort.Strings(o.Status.Namespaces)
7777
return strings.Join(o.Status.Namespaces, ",")
7878
}
79+
80+
// IsServiceAccountSpecified returns true if the spec has a service account name specified.
81+
func (o *OperatorGroup) IsServiceAccountSpecified() bool {
82+
if o.Spec.ServiceAccountName == "" {
83+
return false
84+
}
85+
86+
return true
87+
}
88+
89+
// HasServiceAccountSynced returns true if the service account specified has been synced.
90+
func (o *OperatorGroup) HasServiceAccountSynced() bool {
91+
if o.IsServiceAccountSpecified() && o.Status.ServiceAccountRef != nil {
92+
return true
93+
}
94+
95+
return false
96+
}

pkg/controller/operators/catalog/operator.go

Lines changed: 73 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import (
2323
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2424
"k8s.io/client-go/informers"
2525
"k8s.io/client-go/tools/cache"
26+
"k8s.io/client-go/tools/clientcmd"
2627
"k8s.io/client-go/util/workqueue"
2728

2829
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/reference"
2930
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
30-
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client"
3131
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
3232
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions"
3333
olmerrors "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/errors"
@@ -39,6 +39,7 @@ import (
3939
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
4040
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
4141
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
42+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/scoped"
4243
"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
4344
)
4445

@@ -75,6 +76,8 @@ type Operator struct {
7576
resolver resolver.Resolver
7677
reconciler reconciler.RegistryReconcilerFactory
7778
csvProvidedAPIsIndexer map[string]cache.Indexer
79+
clientAttenuator *scoped.ClientAttenuator
80+
serviceAccountQuerier *scoped.UserDefinedServiceAccountQuerier
7881
}
7982

8083
// NewOperator creates a new Catalog Operator.
@@ -84,8 +87,13 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
8487
watchedNamespaces = []string{metav1.NamespaceAll}
8588
}
8689

90+
config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
91+
if err != nil {
92+
return nil, err
93+
}
94+
8795
// Create a new client for OLM types (CRs)
88-
crClient, err := client.NewClient(kubeconfigPath)
96+
crClient, err := versioned.NewForConfig(config)
8997
if err != nil {
9098
return nil, err
9199
}
@@ -114,6 +122,8 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
114122
catsrcQueueSet: queueinformer.NewEmptyResourceQueueSet(),
115123
subQueueSet: queueinformer.NewEmptyResourceQueueSet(),
116124
csvProvidedAPIsIndexer: map[string]cache.Indexer{},
125+
serviceAccountQuerier: scoped.NewUserDefinedServiceAccountQuerier(logger, crClient),
126+
clientAttenuator: scoped.NewClientAttenuator(logger, config, opClient, crClient),
117127
}
118128
op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, opClient, configmapRegistryImage, op.now)
119129

@@ -1037,6 +1047,18 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
10371047
return err
10381048
}
10391049

1050+
// Does the namespace have an operator group that specifies a user defined
1051+
// service account? If so, then we should use a scoped client for plan
1052+
// execution.
1053+
getter := o.serviceAccountQuerier.NamespaceQuerier(namespace)
1054+
kubeclient, crclient, err := o.clientAttenuator.AttenuateClient(getter)
1055+
if err != nil {
1056+
o.logger.Errorf("failed to get a client for plan execution- %v", err)
1057+
return err
1058+
}
1059+
1060+
ensurer := newStepEnsurer(kubeclient, crclient)
1061+
10401062
for i, step := range plan.Status.Plan {
10411063
switch step.Status {
10421064
case v1alpha1.StepStatusPresent, v1alpha1.StepStatusCreated:
@@ -1112,16 +1134,14 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
11121134

11131135
// Attempt to create the CSV.
11141136
csv.SetNamespace(namespace)
1115-
_, err = o.client.OperatorsV1alpha1().ClusterServiceVersions(csv.GetNamespace()).Create(&csv)
1116-
if k8serrors.IsAlreadyExists(err) {
1117-
// If it already existed, mark the step as Present.
1118-
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1119-
} else if err != nil {
1120-
return errorwrap.Wrapf(err, "error creating csv %s", csv.GetName())
1121-
} else {
1122-
// If no error occurred, mark the step as Created.
1123-
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
1137+
1138+
status, err := ensurer.EnsureClusterServiceVersion(&csv)
1139+
if err != nil {
1140+
return err
11241141
}
1142+
1143+
plan.Status.Plan[i].Status = status
1144+
11251145
case v1alpha1.SubscriptionKind:
11261146
// Marshal the manifest into a subscription instance.
11271147
var sub v1alpha1.Subscription
@@ -1139,47 +1159,22 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
11391159

11401160
// Attempt to create the Subscription
11411161
sub.SetNamespace(namespace)
1142-
_, err = o.client.OperatorsV1alpha1().Subscriptions(sub.GetNamespace()).Create(&sub)
1143-
if k8serrors.IsAlreadyExists(err) {
1144-
// If it already existed, mark the step as Present.
1145-
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1146-
} else if err != nil {
1147-
return errorwrap.Wrapf(err, "error creating subscription %s", sub.GetName())
1148-
} else {
1149-
// If no error occurred, mark the step as Created.
1150-
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
1151-
}
1152-
case secretKind:
1153-
// TODO: this will confuse bundle users that include secrets in their bundles - this only handles pull secrets
1154-
// Get the pre-existing secret.
1155-
secret, err := o.opClient.KubernetesInterface().CoreV1().Secrets(o.namespace).Get(step.Resource.Name, metav1.GetOptions{})
1156-
if k8serrors.IsNotFound(err) {
1157-
return fmt.Errorf("secret %s does not exist", step.Resource.Name)
1158-
} else if err != nil {
1159-
return errorwrap.Wrapf(err, "error getting pull secret from olm namespace %s", secret.GetName())
1162+
1163+
status, err := ensurer.EnsureSubscription(&sub)
1164+
if err != nil {
1165+
return err
11601166
}
11611167

1162-
// Set the namespace to the InstallPlan's namespace and attempt to
1163-
// create a new secret.
1164-
secret.SetNamespace(namespace)
1165-
_, err = o.opClient.KubernetesInterface().CoreV1().Secrets(plan.Namespace).Create(&corev1.Secret{
1166-
ObjectMeta: metav1.ObjectMeta{
1167-
Name: secret.Name,
1168-
Namespace: plan.Namespace,
1169-
},
1170-
Data: secret.Data,
1171-
Type: secret.Type,
1172-
})
1173-
if k8serrors.IsAlreadyExists(err) {
1174-
// If it already existed, mark the step as Present.
1175-
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1176-
} else if err != nil {
1168+
plan.Status.Plan[i].Status = status
1169+
1170+
case secretKind:
1171+
status, err := ensurer.EnsureSecret(o.namespace, plan.GetNamespace(), step.Resource.Name)
1172+
if err != nil {
11771173
return err
1178-
} else {
1179-
// If no error occured, mark the step as Created.
1180-
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
11811174
}
11821175

1176+
plan.Status.Plan[i].Status = status
1177+
11831178
case clusterRoleKind:
11841179
// Marshal the manifest into a ClusterRole instance.
11851180
var cr rbacv1.ClusterRole
@@ -1188,23 +1183,13 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
11881183
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
11891184
}
11901185

1191-
// Attempt to create the ClusterRole.
1192-
_, err = o.opClient.KubernetesInterface().RbacV1().ClusterRoles().Create(&cr)
1193-
if k8serrors.IsAlreadyExists(err) {
1194-
// if we're updating, point owner to the newest csv
1195-
cr.Labels[ownerutil.OwnerKey] = step.Resolving
1196-
_, err = o.opClient.UpdateClusterRole(&cr)
1197-
if err != nil {
1198-
return errorwrap.Wrapf(err, "error updating clusterrole %s", cr.GetName())
1199-
}
1200-
// If it already existed, mark the step as Present.
1201-
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1202-
} else if err != nil {
1203-
return errorwrap.Wrapf(err, "error creating clusterrole %s", cr.GetName())
1204-
} else {
1205-
// If no error occurred, mark the step as Created.
1206-
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
1186+
status, err := ensurer.EnsureClusterRole(&cr, step)
1187+
if err != nil {
1188+
return err
12071189
}
1190+
1191+
plan.Status.Plan[i].Status = status
1192+
12081193
case clusterRoleBindingKind:
12091194
// Marshal the manifest into a RoleBinding instance.
12101195
var rb rbacv1.ClusterRoleBinding
@@ -1213,25 +1198,13 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
12131198
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
12141199
}
12151200

1216-
// Attempt to create the ClusterRoleBinding.
1217-
_, err = o.opClient.KubernetesInterface().RbacV1().ClusterRoleBindings().Create(&rb)
1218-
if k8serrors.IsAlreadyExists(err) {
1219-
// if we're updating, point owner to the newest csv
1220-
rb.Labels[ownerutil.OwnerKey] = step.Resolving
1221-
_, err = o.opClient.UpdateClusterRoleBinding(&rb)
1222-
if err != nil {
1223-
return errorwrap.Wrapf(err, "error updating clusterrolebinding %s", rb.GetName())
1224-
}
1225-
1226-
// If it already existed, mark the step as Present.
1227-
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1228-
} else if err != nil {
1229-
return errorwrap.Wrapf(err, "error creating clusterrolebinding %s", rb.GetName())
1230-
} else {
1231-
// If no error occurred, mark the step as Created.
1232-
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
1201+
status, err := ensurer.EnsureClusterRoleBinding(&rb, step)
1202+
if err != nil {
1203+
return err
12331204
}
12341205

1206+
plan.Status.Plan[i].Status = status
1207+
12351208
case roleKind:
12361209
// Marshal the manifest into a Role instance.
12371210
var r rbacv1.Role
@@ -1248,24 +1221,13 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
12481221
r.SetOwnerReferences(updated)
12491222
r.SetNamespace(namespace)
12501223

1251-
// Attempt to create the Role.
1252-
_, err = o.opClient.KubernetesInterface().RbacV1().Roles(plan.Namespace).Create(&r)
1253-
if k8serrors.IsAlreadyExists(err) {
1254-
// If it already existed, mark the step as Present.
1255-
r.SetNamespace(plan.Namespace)
1256-
_, err = o.opClient.UpdateRole(&r)
1257-
if err != nil {
1258-
return errorwrap.Wrapf(err, "error updating role %s", r.GetName())
1259-
}
1260-
1261-
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1262-
} else if err != nil {
1263-
return errorwrap.Wrapf(err, "error creating role %s", r.GetName())
1264-
} else {
1265-
// If no error occurred, mark the step as Created.
1266-
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
1224+
status, err := ensurer.EnsureRole(plan.Namespace, &r)
1225+
if err != nil {
1226+
return err
12671227
}
12681228

1229+
plan.Status.Plan[i].Status = status
1230+
12691231
case roleBindingKind:
12701232
// Marshal the manifest into a RoleBinding instance.
12711233
var rb rbacv1.RoleBinding
@@ -1282,24 +1244,13 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
12821244
rb.SetOwnerReferences(updated)
12831245
rb.SetNamespace(namespace)
12841246

1285-
// Attempt to create the RoleBinding.
1286-
_, err = o.opClient.KubernetesInterface().RbacV1().RoleBindings(plan.Namespace).Create(&rb)
1287-
if k8serrors.IsAlreadyExists(err) {
1288-
rb.SetNamespace(plan.Namespace)
1289-
_, err = o.opClient.UpdateRoleBinding(&rb)
1290-
if err != nil {
1291-
return errorwrap.Wrapf(err, "error updating rolebinding %s", rb.GetName())
1292-
}
1293-
1294-
// If it already existed, mark the step as Present.
1295-
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1296-
} else if err != nil {
1297-
return errorwrap.Wrapf(err, "error creating rolebinding %s", rb.GetName())
1298-
} else {
1299-
// If no error occurred, mark the step as Created.
1300-
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
1247+
status, err := ensurer.EnsureRoleBinding(plan.Namespace, &rb)
1248+
if err != nil {
1249+
return err
13011250
}
13021251

1252+
plan.Status.Plan[i].Status = status
1253+
13031254
case serviceAccountKind:
13041255
// Marshal the manifest into a ServiceAccount instance.
13051256
var sa corev1.ServiceAccount
@@ -1316,25 +1267,13 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
13161267
sa.SetOwnerReferences(updated)
13171268
sa.SetNamespace(namespace)
13181269

1319-
// Attempt to create the ServiceAccount.
1320-
_, err = o.opClient.KubernetesInterface().CoreV1().ServiceAccounts(plan.Namespace).Create(&sa)
1321-
if k8serrors.IsAlreadyExists(err) {
1322-
// If it already exists we need to patch the existing SA with the new OwnerReferences
1323-
sa.SetNamespace(plan.Namespace)
1324-
_, err = o.opClient.UpdateServiceAccount(&sa)
1325-
if err != nil {
1326-
return errorwrap.Wrapf(err, "error updating service account: %s", sa.GetName())
1327-
}
1328-
1329-
// Mark as present
1330-
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1331-
} else if err != nil {
1332-
return errorwrap.Wrapf(err, "error creating service account: %s", sa.GetName())
1333-
} else {
1334-
// If no error occurred, mark the step as Created.
1335-
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
1270+
status, err := ensurer.EnsureServiceAccount(namespace, &sa)
1271+
if err != nil {
1272+
return err
13361273
}
13371274

1275+
plan.Status.Plan[i].Status = status
1276+
13381277
case serviceKind:
13391278
// Marshal the manifest into a Service instance
13401279
var s corev1.Service
@@ -1351,25 +1290,13 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
13511290
s.SetOwnerReferences(updated)
13521291
s.SetNamespace(namespace)
13531292

1354-
// Attempt to create the Service
1355-
_, err = o.opClient.KubernetesInterface().CoreV1().Services(plan.Namespace).Create(&s)
1356-
if k8serrors.IsAlreadyExists(err) {
1357-
// If it already exists we need to patch the existing SA with the new OwnerReferences
1358-
s.SetNamespace(plan.Namespace)
1359-
_, err = o.opClient.UpdateService(&s)
1360-
if err != nil {
1361-
return errorwrap.Wrapf(err, "error updating service: %s", s.GetName())
1362-
}
1363-
1364-
// Mark as present
1365-
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1366-
} else if err != nil {
1367-
return errorwrap.Wrapf(err, "error creating service: %s", s.GetName())
1368-
} else {
1369-
// If no error occurred, mark the step as Created
1370-
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
1293+
status, err := ensurer.EnsureService(namespace, &s)
1294+
if err != nil {
1295+
return err
13711296
}
13721297

1298+
plan.Status.Plan[i].Status = status
1299+
13731300
default:
13741301
return v1alpha1.ErrInvalidInstallPlan
13751302
}

pkg/controller/operators/catalog/operator_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
appsv1 "k8s.io/api/apps/v1"
1616
corev1 "k8s.io/api/core/v1"
1717
rbacv1 "k8s.io/api/rbac/v1"
18+
"k8s.io/client-go/rest"
1819
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
1920
apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -39,6 +40,7 @@ import (
3940
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
4041
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
4142
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
43+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/scoped"
4244
)
4345

4446
type mockTransitioner struct {
@@ -663,6 +665,8 @@ func NewFakeOperator(ctx context.Context, namespace string, watchedNamespaces []
663665

664666
}
665667

668+
logger := logrus.New()
669+
666670
// Create the new operator
667671
queueOperator, err := queueinformer.NewOperator(opClientFake.KubernetesInterface().Discovery())
668672
for _, informer := range sharedInformers {
@@ -685,6 +689,8 @@ func NewFakeOperator(ctx context.Context, namespace string, watchedNamespaces []
685689
), "resolver"),
686690
sources: make(map[resolver.CatalogKey]resolver.SourceRef),
687691
resolver: &fakes.FakeResolver{},
692+
clientAttenuator: scoped.NewClientAttenuator(logger, &rest.Config{}, opClientFake, clientFake),
693+
serviceAccountQuerier: scoped.NewUserDefinedServiceAccountQuerier(logger, clientFake),
688694
}
689695
op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, op.opClient, "test:pod", op.now)
690696

0 commit comments

Comments
 (0)