Skip to content

Commit

Permalink
Share controller code further
Browse files Browse the repository at this point in the history
  • Loading branch information
halim-lee committed Jul 19, 2022
1 parent 6aef030 commit 061430a
Show file tree
Hide file tree
Showing 3 changed files with 400 additions and 245 deletions.
1 change: 1 addition & 0 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ type BaseComponentService interface {
type BaseComponentNetworkPolicy interface {
GetNamespaceLabels() map[string]string
GetFromLabels() map[string]string
IsDisabled() bool
}

// BaseComponentMonitoring represents basic service monitoring configuration
Expand Down
269 changes: 24 additions & 245 deletions controllers/runtimecomponent_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ import (
"context"
"fmt"
"os"
"strings"

"github.com/application-stacks/runtime-component-operator/common"
"github.com/pkg/errors"

"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
Expand All @@ -50,7 +48,6 @@ import (
networkingv1 "k8s.io/api/networking/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
Expand Down Expand Up @@ -118,7 +115,7 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req

// Fetch the RuntimeComponent instance
instance := &appstacksv1beta2.RuntimeComponent{}
var ba common.BaseComponent = instance
// var ba common.BaseComponent = instance
err = r.GetClient().Get(context.TODO(), req.NamespacedName, instance)
if err != nil {
if kerrors.IsNotFound(err) {
Expand Down Expand Up @@ -163,64 +160,13 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req
Namespace: instance.Namespace,
}

imageReferenceOld := instance.Status.ImageReference
instance.Status.ImageReference = instance.Spec.ApplicationImage
if r.IsOpenShift() {
image, err := imageutil.ParseDockerImageReference(instance.Spec.ApplicationImage)
if err == nil {
isTag := &imagev1.ImageStreamTag{}
isTagName := imageutil.JoinImageStreamTag(image.Name, image.Tag)
isTagNamespace := image.Namespace
if isTagNamespace == "" {
isTagNamespace = instance.Namespace
}
key := types.NamespacedName{Name: isTagName, Namespace: isTagNamespace}
err = r.GetAPIReader().Get(context.Background(), key, isTag)
// Call ManageError only if the error type is not found or is not forbidden. Forbidden could happen
// when the operator tries to call GET for ImageStreamTags on a namespace that doesn't exists (e.g.
// cannot get imagestreamtags.image.openshift.io in the namespace "navidsh": no RBAC policy matched)
if err == nil {
image := isTag.Image
if image.DockerImageReference != "" {
instance.Status.ImageReference = image.DockerImageReference
}
} else if err != nil && !kerrors.IsNotFound(err) && !kerrors.IsForbidden(err) && !strings.Contains(isTagName, "/") {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
}
}
if imageReferenceOld != instance.Status.ImageReference {
reqLogger.Info("Updating status.imageReference", "status.imageReference", instance.Status.ImageReference)
err = r.UpdateStatus(instance)
if err != nil {
reqLogger.Error(err, "Error updating RuntimeComponent status")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
}

if instance.Spec.ServiceAccountName == nil || *instance.Spec.ServiceAccountName == "" {
serviceAccount := &corev1.ServiceAccount{ObjectMeta: defaultMeta}
err = r.CreateOrUpdate(serviceAccount, instance, func() error {
return appstacksutils.CustomizeServiceAccount(serviceAccount, instance, r.GetClient())
})
if err != nil {
reqLogger.Error(err, "Failed to reconcile ServiceAccount")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
} else {
serviceAccount := &corev1.ServiceAccount{ObjectMeta: defaultMeta}
err = r.DeleteResource(serviceAccount)
if err != nil {
reqLogger.Error(err, "Failed to delete ServiceAccount")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
if err = r.UpdateImageReference(instance); err != nil {
reqLogger.Error(err, "Error updating RuntimeComponent")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

// Check if the ServiceAccount has a valid pull secret before creating the deployment/statefulset
// or setting up knative. Otherwise the pods can go into an ImagePullBackOff loop
saErr := appstacksutils.ServiceAccountPullSecretExists(instance, r.GetClient())
if saErr != nil {
return r.ManageError(saErr, common.StatusConditionTypeReconciled, instance)
if err = r.UpdateServiceAccount(instance, defaultMeta); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

isKnativeSupported, err := r.IsGroupVersionSupported(servingv1.SchemeGroupVersion.String(), "Service")
Expand All @@ -231,47 +177,12 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req
}

if instance.Spec.CreateKnativeService != nil && *instance.Spec.CreateKnativeService {
// Clean up non-Knative resources
resources := []client.Object{
&corev1.Service{ObjectMeta: defaultMeta},
&corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: instance.Name + "-headless", Namespace: instance.Namespace}},
&appsv1.Deployment{ObjectMeta: defaultMeta},
&appsv1.StatefulSet{ObjectMeta: defaultMeta},
&autoscalingv1.HorizontalPodAutoscaler{ObjectMeta: defaultMeta},
}
err = r.DeleteResources(resources)
err = r.UpdateKnativeService(instance, defaultMeta, isKnativeSupported)
if err != nil {
reqLogger.Error(err, "Failed to clean up non-Knative resources")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

if ok, _ := r.IsGroupVersionSupported(networkingv1.SchemeGroupVersion.String(), "Ingress"); ok {
r.DeleteResource(&networkingv1.Ingress{ObjectMeta: defaultMeta})
}

if r.IsOpenShift() {
route := &routev1.Route{ObjectMeta: defaultMeta}
err = r.DeleteResource(route)
if err != nil {
reqLogger.Error(err, "Failed to clean up non-Knative resource Route")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
}

if isKnativeSupported {
ksvc := &servingv1.Service{ObjectMeta: defaultMeta}
err = r.CreateOrUpdate(ksvc, instance, func() error {
appstacksutils.CustomizeKnativeService(ksvc, instance)
return nil
})

if err != nil {
reqLogger.Error(err, "Failed to reconcile Knative Service")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
} else {
return r.ManageSuccess(common.StatusConditionTypeReconciled, instance)
}
return r.ManageError(errors.New("failed to reconcile Knative service as operator could not find Knative CRDs"), common.StatusConditionTypeReconciled, instance)
}

if isKnativeSupported {
Expand All @@ -283,75 +194,25 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req
}
}

useCertmanager, err := r.GenerateSvcCertSecret(ba, "rco", "Runtime Component Operator", "runtime-component-operator")
useCertmanager, err := r.UpdateSvcCertSecret(instance, "rco", "Runtime Component Operator", "runtime-component-operator")
if err != nil {
reqLogger.Error(err, "Failed to reconcile CertManager Certificate")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
if ba.GetService().GetCertificateSecretRef() != nil {
ba.GetStatus().SetReference(common.StatusReferenceCertSecretName, *ba.GetService().GetCertificateSecretRef())
}

svc := &corev1.Service{ObjectMeta: defaultMeta}
err = r.CreateOrUpdate(svc, instance, func() error {
appstacksutils.CustomizeService(svc, ba)
svc.Annotations = appstacksutils.MergeMaps(svc.Annotations, instance.Spec.Service.Annotations)
if !useCertmanager && r.IsOpenShift() {
appstacksutils.AddOCPCertAnnotation(ba, svc)
}
monitoringEnabledLabelName := getMonitoringEnabledLabelName(ba)
if instance.Spec.Monitoring != nil {
svc.Labels[monitoringEnabledLabelName] = "true"
} else {
delete(svc.Labels, monitoringEnabledLabelName)
}
return nil
})
if err != nil {
reqLogger.Error(err, "Failed to reconcile Service")
if err = r.UpdateService(instance, defaultMeta, useCertmanager); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

networkPolicy := &networkingv1.NetworkPolicy{ObjectMeta: defaultMeta}
if np := instance.Spec.NetworkPolicy; !np.IsDisabled() {
err = r.CreateOrUpdate(networkPolicy, instance, func() error {
appstacksutils.CustomizeNetworkPolicy(networkPolicy, r.IsOpenShift(), instance)
return nil
})
if err != nil {
reqLogger.Error(err, "Failed to reconcile network policy")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
} else {
if err := r.DeleteResource(networkPolicy); err != nil {
reqLogger.Error(err, "Failed to delete network policy")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
if err = r.UpdateNetworkPolicy(instance, defaultMeta); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

err = r.ReconcileBindings(instance)
if err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, ba)
if err = r.ReconcileBindings(instance); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

if instance.Spec.StatefulSet != nil {
// Delete Deployment if exists
deploy := &appsv1.Deployment{ObjectMeta: defaultMeta}
err = r.DeleteResource(deploy)

if err != nil {
reqLogger.Error(err, "Failed to delete Deployment")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
svc := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: instance.Name + "-headless", Namespace: instance.Namespace}}
err = r.CreateOrUpdate(svc, instance, func() error {
appstacksutils.CustomizeService(svc, instance)
svc.Spec.ClusterIP = corev1.ClusterIPNone
svc.Spec.Type = corev1.ServiceTypeClusterIP
return nil
})
if err != nil {
reqLogger.Error(err, "Failed to reconcile headless Service")
if err = r.UpdateStatefulSetReq(instance, defaultMeta); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

Expand All @@ -369,24 +230,11 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req
reqLogger.Error(err, "Failed to reconcile StatefulSet")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

} else {
// Delete StatefulSet if exists
statefulSet := &appsv1.StatefulSet{ObjectMeta: defaultMeta}
err = r.DeleteResource(statefulSet)
if err != nil {
reqLogger.Error(err, "Failed to delete Statefulset")
if err = r.UpdateDeploymentReq(instance, defaultMeta); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

// Delete StatefulSet if exists
headlesssvc := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: instance.Name + "-headless", Namespace: instance.Namespace}}
err = r.DeleteResource(headlesssvc)

if err != nil {
reqLogger.Error(err, "Failed to delete headless Service")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
deploy := &appsv1.Deployment{ObjectMeta: defaultMeta}
err = r.CreateOrUpdate(deploy, instance, func() error {
appstacksutils.CustomizeDeployment(deploy, instance)
Expand All @@ -400,79 +248,26 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req
reqLogger.Error(err, "Failed to reconcile Deployment")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

}

if instance.Spec.Autoscaling != nil {
hpa := &autoscalingv1.HorizontalPodAutoscaler{ObjectMeta: defaultMeta}
err = r.CreateOrUpdate(hpa, instance, func() error {
appstacksutils.CustomizeHPA(hpa, instance)
return nil
})

if err != nil {
reqLogger.Error(err, "Failed to reconcile HorizontalPodAutoscaler")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
} else {
hpa := &autoscalingv1.HorizontalPodAutoscaler{ObjectMeta: defaultMeta}
err = r.DeleteResource(hpa)
if err != nil {
reqLogger.Error(err, "Failed to delete HorizontalPodAutoscaler")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
if err = r.UpdateAutoscaling(instance, defaultMeta); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

if ok, err := r.IsGroupVersionSupported(routev1.SchemeGroupVersion.String(), "Route"); err != nil {
reqLogger.Error(err, fmt.Sprintf("Failed to check if %s is supported", routev1.SchemeGroupVersion.String()))
r.ManageError(err, common.StatusConditionTypeReconciled, instance)
} else if ok {
if instance.Spec.Expose != nil && *instance.Spec.Expose {
route := &routev1.Route{ObjectMeta: defaultMeta}
err = r.CreateOrUpdate(route, instance, func() error {
key, cert, caCert, destCACert, err := r.GetRouteTLSValues(ba)
if err != nil {
return err
}
appstacksutils.CustomizeRoute(route, ba, key, cert, caCert, destCACert)

return nil
})
if err != nil {
reqLogger.Error(err, "Failed to reconcile Route")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
} else {
route := &routev1.Route{ObjectMeta: defaultMeta}
err = r.DeleteResource(route)
if err != nil {
reqLogger.Error(err, "Failed to delete Route")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
if err = r.UpdateRoute(instance, defaultMeta); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
} else {

if ok, err := r.IsGroupVersionSupported(networkingv1.SchemeGroupVersion.String(), "Ingress"); err != nil {
reqLogger.Error(err, fmt.Sprintf("Failed to check if %s is supported", networkingv1.SchemeGroupVersion.String()))
r.ManageError(err, common.StatusConditionTypeReconciled, instance)
} else if ok {
if instance.Spec.Expose != nil && *instance.Spec.Expose {
ing := &networkingv1.Ingress{ObjectMeta: defaultMeta}
err = r.CreateOrUpdate(ing, instance, func() error {
appstacksutils.CustomizeIngress(ing, instance)
return nil
})
if err != nil {
reqLogger.Error(err, "Failed to reconcile Ingress")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
} else {
ing := &networkingv1.Ingress{ObjectMeta: defaultMeta}
err = r.DeleteResource(ing)
if err != nil {
reqLogger.Error(err, "Failed to delete Ingress")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
if err = r.UpdateIngress(instance, defaultMeta); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
}
}
Expand All @@ -481,25 +276,9 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req
reqLogger.Error(err, fmt.Sprintf("Failed to check if %s is supported", prometheusv1.SchemeGroupVersion.String()))
r.ManageError(err, common.StatusConditionTypeReconciled, instance)
} else if ok {
if instance.Spec.Monitoring != nil && (instance.Spec.CreateKnativeService == nil || !*instance.Spec.CreateKnativeService) {
sm := &prometheusv1.ServiceMonitor{ObjectMeta: defaultMeta}
err = r.CreateOrUpdate(sm, instance, func() error {
appstacksutils.CustomizeServiceMonitor(sm, instance)
return nil
})
if err != nil {
reqLogger.Error(err, "Failed to reconcile ServiceMonitor")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
} else {
sm := &prometheusv1.ServiceMonitor{ObjectMeta: defaultMeta}
err = r.DeleteResource(sm)
if err != nil {
reqLogger.Error(err, "Failed to delete ServiceMonitor")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}
if err = r.UpdateServiceMonitor(instance, defaultMeta); err != nil {
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

} else {
reqLogger.V(1).Info(fmt.Sprintf("%s is not supported", prometheusv1.SchemeGroupVersion.String()))
}
Expand Down
Loading

0 comments on commit 061430a

Please sign in to comment.