Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
awgreene committed Dec 1, 2020
1 parent 2b104d9 commit a8cf38d
Show file tree
Hide file tree
Showing 11 changed files with 824 additions and 8 deletions.
26 changes: 26 additions & 0 deletions cmd/olm/manager.go
Expand Up @@ -23,6 +23,32 @@ func Manager(ctx context.Context) (ctrl.Manager, error) {
return nil, err
}

operatorConditionReconciler, err := operators.NewOperatorConditionReconciler(
mgr.GetClient(),
ctrl.Log.WithName("controllers").WithName("operatorcondition"),
mgr.GetScheme(),
)
if err != nil {
return nil, err
}

if err = operatorConditionReconciler.SetupWithManager(mgr); err != nil {
return nil, err
}

operatorConditionGenReconciler, err := operators.NewOperatorConditionGenReconciler(
mgr.GetClient(),
ctrl.Log.WithName("controllers").WithName("operatorcondition-generator"),
mgr.GetScheme(),
)
if err != nil {
return nil, err
}

if err = operatorConditionGenReconciler.SetupWithManager(mgr); err != nil {
return nil, err
}

// Setup a new controller to reconcile Operators
setupLog.Info("configuring controller")
if feature.Gate.Enabled(feature.OperatorLifecycleManagerV1) {
Expand Down
2 changes: 2 additions & 0 deletions pkg/controller/operators/adoption_controller.go
Expand Up @@ -82,6 +82,7 @@ func (r *AdoptionReconciler) SetupWithManager(mgr ctrl.Manager) error {
Watches(&source.Kind{Type: &apiextensionsv1.CustomResourceDefinition{}}, enqueueProviders).
Watches(&source.Kind{Type: &apiregistrationv1.APIService{}}, enqueueCSV).
Watches(&source.Kind{Type: &operatorsv1alpha1.Subscription{}}, enqueueCSV).
Watches(&source.Kind{Type: &operatorsv1.OperatorCondition{}}, enqueueCSV).
Complete(reconcile.Func(r.ReconcileClusterServiceVersion))
if err != nil {
return err
Expand Down Expand Up @@ -324,6 +325,7 @@ func (r *AdoptionReconciler) adoptees(ctx context.Context, operator decorators.O
&operatorsv1alpha1.SubscriptionList{},
&operatorsv1alpha1.InstallPlanList{},
&operatorsv1alpha1.ClusterServiceVersionList{},
&operatorsv1.OperatorConditionList{},
}

// Only resources that aren't already labelled are adoption candidates
Expand Down
3 changes: 2 additions & 1 deletion pkg/controller/operators/operator_controller.go
Expand Up @@ -33,7 +33,6 @@ var (
apiregistrationv1.AddToScheme,
operatorsv1alpha1.AddToScheme,
operatorsv1.AddToScheme,
operatorsv1.AddToScheme,
)

// AddToScheme adds all types necessary for the controller to operate.
Expand Down Expand Up @@ -81,6 +80,7 @@ func (r *OperatorReconciler) SetupWithManager(mgr ctrl.Manager) error {
Watches(&source.Kind{Type: &operatorsv1alpha1.Subscription{}}, enqueueOperator).
Watches(&source.Kind{Type: &operatorsv1alpha1.InstallPlan{}}, enqueueOperator).
Watches(&source.Kind{Type: &operatorsv1alpha1.ClusterServiceVersion{}}, enqueueOperator).
Watches(&source.Kind{Type: &operatorsv1.OperatorCondition{}}, enqueueOperator).
// TODO(njhale): Add WebhookConfigurations and ConfigMaps
Complete(r)
}
Expand Down Expand Up @@ -205,6 +205,7 @@ func (r *OperatorReconciler) listComponents(ctx context.Context, selector labels
&operatorsv1alpha1.SubscriptionList{},
&operatorsv1alpha1.InstallPlanList{},
&operatorsv1alpha1.ClusterServiceVersionList{},
&operatorsv1.OperatorConditionList{},
}

opt := client.MatchingLabelsSelector{Selector: selector}
Expand Down
250 changes: 250 additions & 0 deletions pkg/controller/operators/operatorcondition_controller.go
@@ -0,0 +1,250 @@
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(2).Info("reconciling operatorcondition")

operatorCondition := &operatorsv1.OperatorCondition{}
err := r.Client.Get(context.TODO(), req.NamespacedName, operatorCondition)
if err != nil {
log.V(1).Error(err, "Unable to find operatorcondition")
return ctrl.Result{}, err
}

err = r.ensureOperatorConditionRole(operatorCondition)
if err != nil {
log.V(1).Error(err, "Error ensuring OperatorCondition Role")
return ctrl.Result{Requeue: true}, err
}

err = r.ensureOperatorConditionRoleBinding(operatorCondition)
if err != nil {
log.V(1).Error(err, "Error ensuring OperatorCondition RoleBinding")
return ctrl.Result{Requeue: true}, err
}

err = r.ensureDeploymentEnvVars(operatorCondition)
if err != nil {
log.V(1).Error(err, "Error ensuring OperatorCondition Deployment EnvVars")
return ctrl.Result{Requeue: true}, err
}

return ctrl.Result{}, nil
}

func (r *OperatorConditionReconciler) ensureOperatorConditionRole(operatorCondition *operatorsv1.OperatorCondition) error {
r.log.V(4).Info("Creating the RBAC for the operatorCondition")

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(role.Rules, existingRole.Rules) {
r.log.V(5).Info("Existing Role does not need to be updated")
return nil
}
r.log.V(5).Info("Existing Role needs to be updated")

existingRole.OwnerReferences = role.OwnerReferences
existingRole.Rules = role.Rules
err = r.Client.Update(context.TODO(), existingRole)
if err != nil {
return err
}
}

return nil
}

func (r *OperatorConditionReconciler) ensureOperatorConditionRoleBinding(operatorCondition *operatorsv1.OperatorCondition) error {
r.log.V(4).Info("Creating the RoleBinding for the OperatorCondition")
subjects := []rbacv1.Subject{}
for _, serviceAccount := range operatorCondition.Spec.ServiceAccounts {
subjects = append(subjects, rbacv1.Subject{
Kind: rbacv1.ServiceAccountKind,
Name: serviceAccount,
APIGroup: "",
})
}

roleBinding := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: operatorCondition.GetName(),
Namespace: operatorCondition.GetNamespace(),
},
Subjects: subjects,
RoleRef: rbacv1.RoleRef{
Kind: "Role",
Name: operatorCondition.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(roleBinding.Subjects, existingRoleBinding.Subjects) {
r.log.V(5).Info("Existing RoleBinding does not need to be updated")
return nil
}
r.log.V(5).Info("Existing RoleBinding needs to be updated")
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 {
r.log.V(4).Info("Ensuring that deployments have the OPERATOR_CONDITION_NAME variable")
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 := ensureEnvVarIsPresent(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(5).Info("Existing deployment does not need to be updated")
continue
}
err = r.Client.Update(context.TODO(), deployment)
if err != nil {
return err
}
}
return nil
}

func ensureEnvVarIsPresent(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
}

0 comments on commit a8cf38d

Please sign in to comment.